Version Description
- Update src to use ScssPHP github repo at 1.5.2
- Update deprecated setFormatter to setOutputStyle and provide db migration shadoath
Download this release
Release Info
Developer | Sky Bolt |
Plugin | WP-SCSS |
Version | 2.3.0 |
Comparing to | |
See all releases |
Code changes from version 2.2.0 to 2.3.0
- class/class-wp-scss.php +13 -20
- options.php +10 -16
- readme.md +8 -2
- readme.txt +7 -3
- scssphp/LICENSE.md +20 -0
- scssphp/README.md +71 -0
- scssphp/bin/pscss +71 -35
- scssphp/composer.json +89 -0
- scssphp/phpcs.xml.dist +12 -0
- scssphp/scss.inc.php +14 -29
- scssphp/src/Base/Range.php +9 -0
- scssphp/src/Block.php +4 -2
- scssphp/src/Cache.php +41 -9
- scssphp/src/Colors.php +7 -5
- scssphp/src/CompilationResult.php +69 -0
- scssphp/src/Compiler.php +2605 -1185
- scssphp/src/Compiler/CachedResult.php +77 -0
- scssphp/src/Compiler/Environment.php +4 -2
- scssphp/src/Exception/CompilerException.php +2 -0
- scssphp/src/Exception/ParserException.php +2 -0
- scssphp/src/Exception/RangeException.php +2 -0
- scssphp/src/Exception/SassScriptException.php +32 -0
- scssphp/src/Exception/ServerException.php +4 -0
- scssphp/src/Formatter.php +45 -14
- scssphp/src/Formatter/Compact.php +6 -0
- scssphp/src/Formatter/Compressed.php +4 -1
- scssphp/src/Formatter/Crunched.php +8 -1
- scssphp/src/Formatter/Debug.php +6 -1
- scssphp/src/Formatter/Expanded.php +2 -1
- scssphp/src/Formatter/Nested.php +6 -1
- scssphp/src/Formatter/OutputBlock.php +9 -7
- scssphp/src/Logger/LoggerInterface.php +48 -0
- scssphp/src/Logger/QuietLogger.php +27 -0
- scssphp/src/Logger/StreamLogger.php +60 -0
- scssphp/src/Node.php +4 -2
- scssphp/src/Node/Number.php +545 -140
- scssphp/src/OutputStyle.php +9 -0
- scssphp/src/Parser.php +325 -113
- scssphp/src/SourceMap/Base64.php +4 -2
- scssphp/src/SourceMap/Base64VLQ.php +2 -2
- scssphp/src/SourceMap/Base64VLQEncoder.php +0 -217
- scssphp/src/SourceMap/SourceMapGenerator.php +39 -7
- scssphp/src/Type.php +4 -0
- scssphp/src/Util.php +35 -11
- scssphp/src/Util/Path.php +77 -0
- scssphp/src/ValueConverter.php +95 -0
- scssphp/src/Version.php +1 -1
- scssphp/src/Warn.php +84 -0
- wp-scss.php +9 -5
class/class-wp-scss.php
CHANGED
@@ -5,36 +5,28 @@ include_once(WPSCSS_PLUGIN_DIR . '/scssphp/scss.inc.php');
|
|
5 |
use ScssPhp\ScssPhp\Compiler;
|
6 |
|
7 |
class Wp_Scss {
|
8 |
-
/**
|
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
|
18 |
*
|
19 |
* @param string scss_dir - path to source directory for scss files
|
20 |
* @param string css_dir - path to output directory for css files
|
21 |
-
* @param string
|
22 |
*
|
23 |
* @var object scssc - instantiate the compiling object.
|
24 |
*
|
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->
|
38 |
$this->scssc->setImportPaths( $this->scss_dir );
|
39 |
|
40 |
$this->sourcemaps = $sourcemaps;
|
@@ -89,7 +81,7 @@ class Wp_Scss {
|
|
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 '_'
|
@@ -105,7 +97,7 @@ class Wp_Scss {
|
|
105 |
$outputName = preg_replace("/\.[^$]*/", ".css", $scss_file);
|
106 |
$output = $this->css_dir . $outputName;
|
107 |
|
108 |
-
$this->compiler($input, $output
|
109 |
}
|
110 |
|
111 |
if (count($this->compile_errors) < 1) {
|
@@ -141,7 +133,7 @@ class Wp_Scss {
|
|
141 |
* Puts error in 'compile_errors' property
|
142 |
* @access public
|
143 |
*/
|
144 |
-
private function compiler($in, $out
|
145 |
|
146 |
if (!file_exists($this->cache)) {
|
147 |
mkdir($this->cache, 0644);
|
@@ -149,15 +141,16 @@ class Wp_Scss {
|
|
149 |
if (is_writable($this->cache)) {
|
150 |
try {
|
151 |
$map = basename($out) . '.map';
|
152 |
-
$this->scssc->setSourceMap(constant('ScssPhp\ScssPhp\Compiler::' . $
|
153 |
$this->scssc->setSourceMapOptions(array(
|
154 |
-
'sourceMapWriteTo' => $
|
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 |
-
$
|
|
|
161 |
|
162 |
file_put_contents($this->cache . basename($out), $css);
|
163 |
} catch (Exception $e) {
|
@@ -165,14 +158,14 @@ class Wp_Scss {
|
|
165 |
'file' => basename($in),
|
166 |
'message' => $e->getMessage(),
|
167 |
);
|
168 |
-
array_push($
|
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($
|
176 |
}
|
177 |
}
|
178 |
|
@@ -283,6 +276,6 @@ class Wp_Scss {
|
|
283 |
|
284 |
public function set_variables(array $variables) {
|
285 |
|
286 |
-
$this->scssc->
|
287 |
}
|
288 |
} // End Wp_Scss Class
|
5 |
use ScssPhp\ScssPhp\Compiler;
|
6 |
|
7 |
class Wp_Scss {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
/**
|
10 |
* Set values for Wp_Scss::properties
|
11 |
*
|
12 |
* @param string scss_dir - path to source directory for scss files
|
13 |
* @param string css_dir - path to output directory for css files
|
14 |
+
* @param string compile_method - type of compile (compressed or expanded)
|
15 |
*
|
16 |
* @var object scssc - instantiate the compiling object.
|
17 |
*
|
18 |
* @var array compile_errors - catches errors from compile
|
19 |
*/
|
20 |
public function __construct ($scss_dir, $css_dir, $compile_method, $sourcemaps) {
|
21 |
+
|
22 |
$this->scss_dir = $scss_dir;
|
23 |
$this->css_dir = $css_dir;
|
|
|
24 |
$this->compile_errors = array();
|
25 |
$this->scssc = new Compiler();
|
26 |
|
27 |
$this->cache = WPSCSS_PLUGIN_DIR . '/cache/';
|
28 |
|
29 |
+
$this->scssc->setOutputStyle( $compile_method );
|
30 |
$this->scssc->setImportPaths( $this->scss_dir );
|
31 |
|
32 |
$this->sourcemaps = $sourcemaps;
|
81 |
* @access public
|
82 |
*/
|
83 |
public function compile() {
|
84 |
+
|
85 |
$input_files = array();
|
86 |
|
87 |
// Loop through directory and get .scss file that do not start with '_'
|
97 |
$outputName = preg_replace("/\.[^$]*/", ".css", $scss_file);
|
98 |
$output = $this->css_dir . $outputName;
|
99 |
|
100 |
+
$this->compiler($input, $output);
|
101 |
}
|
102 |
|
103 |
if (count($this->compile_errors) < 1) {
|
133 |
* Puts error in 'compile_errors' property
|
134 |
* @access public
|
135 |
*/
|
136 |
+
private function compiler($in, $out) {
|
137 |
|
138 |
if (!file_exists($this->cache)) {
|
139 |
mkdir($this->cache, 0644);
|
141 |
if (is_writable($this->cache)) {
|
142 |
try {
|
143 |
$map = basename($out) . '.map';
|
144 |
+
$this->scssc->setSourceMap(constant('ScssPhp\ScssPhp\Compiler::' . $this->sourcemaps));
|
145 |
$this->scssc->setSourceMapOptions(array(
|
146 |
+
'sourceMapWriteTo' => $this->css_dir . $map, // absolute path to a file to write the map to
|
147 |
'sourceMapURL' => $map, // url of the map
|
148 |
'sourceMapBasepath' => rtrim(ABSPATH, '/'), // base path for filename normalization
|
149 |
'sourceRoot' => home_url('/'), // This value is prepended to the individual entries in the 'source' field.
|
150 |
));
|
151 |
|
152 |
+
$compilationResult = $this->scssc->compileString(file_get_contents($in), $in);
|
153 |
+
$css = $compilationResult->getCss();
|
154 |
|
155 |
file_put_contents($this->cache . basename($out), $css);
|
156 |
} catch (Exception $e) {
|
158 |
'file' => basename($in),
|
159 |
'message' => $e->getMessage(),
|
160 |
);
|
161 |
+
array_push($this->compile_errors, $errors);
|
162 |
}
|
163 |
} else {
|
164 |
$errors = array (
|
165 |
'file' => $this->cache,
|
166 |
'message' => "File Permission Error, permission denied. Please make the cache directory writable."
|
167 |
);
|
168 |
+
array_push($this->compile_errors, $errors);
|
169 |
}
|
170 |
}
|
171 |
|
276 |
|
277 |
public function set_variables(array $variables) {
|
278 |
|
279 |
+
$this->scssc->addVariables(array_map('ScssPhp\ScssPhp\ValueConverter::parseValue', $variables));
|
280 |
}
|
281 |
} // End Wp_Scss Class
|
options.php
CHANGED
@@ -1,6 +1,8 @@
|
|
1 |
<?php
|
2 |
-
|
3 |
-
|
|
|
|
|
4 |
/**
|
5 |
* Holds the values to be used in the fields callbacks
|
6 |
*/
|
@@ -9,8 +11,7 @@ class Wp_Scss_Settings
|
|
9 |
/**
|
10 |
* Start up
|
11 |
*/
|
12 |
-
public function __construct()
|
13 |
-
{
|
14 |
add_action( 'admin_menu', array( $this, 'add_plugin_page' ) );
|
15 |
add_action( 'admin_init', array( $this, 'page_init' ) );
|
16 |
}
|
@@ -18,8 +19,7 @@ class Wp_Scss_Settings
|
|
18 |
/**
|
19 |
* Add options page
|
20 |
*/
|
21 |
-
public function add_plugin_page()
|
22 |
-
{
|
23 |
// This page will be under "Settings"
|
24 |
add_options_page(
|
25 |
'Settings Admin',
|
@@ -33,8 +33,7 @@ class Wp_Scss_Settings
|
|
33 |
/**
|
34 |
* Options page callback
|
35 |
*/
|
36 |
-
public function create_admin_page()
|
37 |
-
{
|
38 |
// Set class property
|
39 |
$this->options = get_option( 'wpscss_options' );
|
40 |
?>
|
@@ -62,8 +61,7 @@ class Wp_Scss_Settings
|
|
62 |
/**
|
63 |
* Register and add settings
|
64 |
*/
|
65 |
-
public function page_init()
|
66 |
-
{
|
67 |
register_setting(
|
68 |
'wpscss_options_group', // Option group
|
69 |
'wpscss_options', // Option name
|
@@ -137,12 +135,8 @@ class Wp_Scss_Settings
|
|
137 |
'name' => 'compiling_options',
|
138 |
'type' => apply_filters( 'wp_scss_compiling_modes',
|
139 |
array(
|
140 |
-
|
141 |
-
|
142 |
-
'ScssPhp\ScssPhp\Formatter\Compressed' => 'Compressed',
|
143 |
-
'ScssPhp\ScssPhp\Formatter\Compact' => 'Compact',
|
144 |
-
'ScssPhp\ScssPhp\Formatter\Crunched' => 'Crunched',
|
145 |
-
'ScssPhp\ScssPhp\Formatter\Debug' => 'Debug'
|
146 |
)
|
147 |
)
|
148 |
)
|
1 |
<?php
|
2 |
+
|
3 |
+
use ScssPhp\ScssPhp\OutputStyle;
|
4 |
+
|
5 |
+
class Wp_Scss_Settings {
|
6 |
/**
|
7 |
* Holds the values to be used in the fields callbacks
|
8 |
*/
|
11 |
/**
|
12 |
* Start up
|
13 |
*/
|
14 |
+
public function __construct() {
|
|
|
15 |
add_action( 'admin_menu', array( $this, 'add_plugin_page' ) );
|
16 |
add_action( 'admin_init', array( $this, 'page_init' ) );
|
17 |
}
|
19 |
/**
|
20 |
* Add options page
|
21 |
*/
|
22 |
+
public function add_plugin_page() {
|
|
|
23 |
// This page will be under "Settings"
|
24 |
add_options_page(
|
25 |
'Settings Admin',
|
33 |
/**
|
34 |
* Options page callback
|
35 |
*/
|
36 |
+
public function create_admin_page() {
|
|
|
37 |
// Set class property
|
38 |
$this->options = get_option( 'wpscss_options' );
|
39 |
?>
|
61 |
/**
|
62 |
* Register and add settings
|
63 |
*/
|
64 |
+
public function page_init() {
|
|
|
65 |
register_setting(
|
66 |
'wpscss_options_group', // Option group
|
67 |
'wpscss_options', // Option name
|
135 |
'name' => 'compiling_options',
|
136 |
'type' => apply_filters( 'wp_scss_compiling_modes',
|
137 |
array(
|
138 |
+
OutputStyle::COMPRESSED => ucfirst(OutputStyle::COMPRESSED),
|
139 |
+
OutputStyle::EXPANDED => ucfirst(OutputStyle::EXPANDED),
|
|
|
|
|
|
|
|
|
140 |
)
|
141 |
)
|
142 |
)
|
readme.md
CHANGED
@@ -24,11 +24,14 @@ Ideally you should setup a scss folder and a css folder within your theme. This
|
|
24 |
|
25 |
#### Compiling Mode
|
26 |
|
27 |
-
Compiling comes in
|
28 |
|
|
|
29 |
- Expanded - Full open css. One line per property. Brackets close on their own line.
|
|
|
|
|
|
|
30 |
- Nested - Lightly compressed css. Brackets close with css block. Indents to match scss nesting.
|
31 |
-
- Compressed - More compressed css. Entire rule block on one line. No indentation.
|
32 |
- Compact - Removes all line breaks, unnecessary whitespace, and single-line comments.
|
33 |
- Crunched - Same as Compressed, but also removes multi-line comments.
|
34 |
|
@@ -104,6 +107,9 @@ This plugin will only work with .scss format.
|
|
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)
|
24 |
|
25 |
#### Compiling Mode
|
26 |
|
27 |
+
Compiling comes in two modes:
|
28 |
|
29 |
+
- Compressed - More compressed css. Entire rule block on one line. No indentation.
|
30 |
- Expanded - Full open css. One line per property. Brackets close on their own line.
|
31 |
+
|
32 |
+
**Removed** compiling modes
|
33 |
+
|
34 |
- Nested - Lightly compressed css. Brackets close with css block. Indents to match scss nesting.
|
|
|
35 |
- Compact - Removes all line breaks, unnecessary whitespace, and single-line comments.
|
36 |
- Crunched - Same as Compressed, but also removes multi-line comments.
|
37 |
|
107 |
|
108 |
## Changelog
|
109 |
|
110 |
+
- 2.3.0
|
111 |
+
- Update src to use [ScssPHP github repo at 1.5.2](https://github.com/scssphp/scssphp/releases/tag/1.5.2)
|
112 |
+
- Update deprecated setFormatter to setOutputStyle and provide db migration [shadoath](https://github.com/ConnectThink/WP-SCSS/pull/195)
|
113 |
- 2.2.0
|
114 |
- Updates to allow compile() from outside the plugin [niaccurshi](https://github.com/ConnectThink/WP-SCSS/pull/190)
|
115 |
- Update src to use [ScssPHP github repo at 1.2.1](https://github.com/scssphp/scssphp/releases/tag/1.2.1)
|
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.
|
9 |
License: GPLv3 or later
|
10 |
License URI: http://www.gnu.org/copyleft/gpl.html
|
11 |
|
@@ -61,7 +61,7 @@ Alternatively, you can include [Bourbon](https://github.com/thoughtbot/bourbon)
|
|
61 |
|
62 |
This plugin will only work with .scss format.
|
63 |
|
64 |
-
|
65 |
= It's not updating my css, what's happening? =
|
66 |
|
67 |
Do you have errors printing to the front end? If not, check your log file in your scss directory. The css will not be updated if there are errors in your sass file(s).
|
@@ -76,10 +76,14 @@ If you are having issues with the plugin, create an issue on [github](https://gi
|
|
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)
|
5 |
Requires at least: 3.0.1
|
6 |
Tested up to: 5.7.1
|
7 |
Requires PHP: 5.6
|
8 |
+
Stable tag: 2.3.0
|
9 |
License: GPLv3 or later
|
10 |
License URI: http://www.gnu.org/copyleft/gpl.html
|
11 |
|
61 |
|
62 |
This plugin will only work with .scss format.
|
63 |
|
64 |
+
|
65 |
= It's not updating my css, what's happening? =
|
66 |
|
67 |
Do you have errors printing to the front end? If not, check your log file in your scss directory. The css will not be updated if there are errors in your sass file(s).
|
76 |
|
77 |
== Changelog ==
|
78 |
|
79 |
+
= 2.3.0 =
|
80 |
+
- Update src to use [ScssPHP github repo at 1.5.2](https://github.com/scssphp/scssphp/releases/tag/1.5.2)
|
81 |
+
- Update deprecated setFormatter to setOutputStyle and provide db migration [shadoath](https://github.com/ConnectThink/WP-SCSS/pull/195)
|
82 |
+
|
83 |
= 2.2.0 =
|
84 |
- Updates to allow compile() from outside the plugin [niaccurshi](https://github.com/ConnectThink/WP-SCSS/pull/190)
|
85 |
- Update src to use [ScssPHP github repo at 1.2.1](https://github.com/scssphp/scssphp/releases/tag/1.2.1)
|
86 |
+
|
87 |
= 2.1.6 =
|
88 |
- 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)
|
89 |
- Allow setting Base Directory to Parent theme folder. [Shadoath](https://github.com/ConnectThink/WP-SCSS/issues/178)
|
scssphp/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright (c) 2015 Leaf Corcoran, http://scssphp.github.io/scssphp
|
2 |
+
|
3 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
4 |
+
a copy of this software and associated documentation files (the
|
5 |
+
"Software"), to deal in the Software without restriction, including
|
6 |
+
without limitation the rights to use, copy, modify, merge, publish,
|
7 |
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8 |
+
permit persons to whom the Software is furnished to do so, subject to
|
9 |
+
the following conditions:
|
10 |
+
|
11 |
+
The above copyright notice and this permission notice shall be
|
12 |
+
included in all copies or substantial portions of the Software.
|
13 |
+
|
14 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17 |
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18 |
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19 |
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20 |
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
scssphp/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
|
13 |
+
`scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing.
|
14 |
+
|
15 |
+
Run the following command from the root directory to run every test:
|
16 |
+
|
17 |
+
vendor/bin/phpunit tests
|
18 |
+
|
19 |
+
There are several tests in the `tests/` directory:
|
20 |
+
|
21 |
+
* `ApiTest.php` contains various unit tests that test the PHP interface.
|
22 |
+
* `ExceptionTest.php` contains unit tests that test for exceptions thrown by the parser and compiler.
|
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
|
30 |
+
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`. 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.
|
scssphp/bin/pscss
CHANGED
@@ -20,6 +20,8 @@ if (version_compare(PHP_VERSION, '5.6') < 0) {
|
|
20 |
include __DIR__ . '/../scss.inc.php';
|
21 |
|
22 |
use ScssPhp\ScssPhp\Compiler;
|
|
|
|
|
23 |
use ScssPhp\ScssPhp\Parser;
|
24 |
use ScssPhp\ScssPhp\Version;
|
25 |
|
@@ -28,10 +30,10 @@ $loadPaths = null;
|
|
28 |
$dumpTree = false;
|
29 |
$inputFile = null;
|
30 |
$changeDir = false;
|
31 |
-
$debugInfo = false;
|
32 |
-
$lineNumbers = false;
|
33 |
$encoding = false;
|
34 |
$sourceMap = false;
|
|
|
|
|
35 |
|
36 |
/**
|
37 |
* Parse argument
|
@@ -60,25 +62,29 @@ function parseArgument(&$i, $options) {
|
|
60 |
}
|
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
|
68 |
-
Usage: $exe [options] [input-file]
|
69 |
|
70 |
Options include:
|
71 |
|
72 |
--help Show this message [-h, -?]
|
73 |
--continue-on-error [deprecated] Ignored
|
74 |
-
--debug-info
|
75 |
-
--dump-tree Dump formatted parse tree [-T]
|
76 |
--iso8859-1 Use iso8859-1 encoding instead of default utf-8
|
77 |
-
--line-numbers
|
78 |
--load-path=PATH Set import path [-I]
|
79 |
--precision=N [deprecated] Ignored. (default 10) [-p]
|
80 |
--sourcemap Create source map file
|
81 |
-
--
|
|
|
|
|
82 |
--version Print the version [-v]
|
83 |
|
84 |
EOT;
|
@@ -95,8 +101,9 @@ EOT;
|
|
95 |
continue;
|
96 |
}
|
97 |
|
|
|
98 |
if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
|
99 |
-
|
100 |
continue;
|
101 |
}
|
102 |
|
@@ -105,8 +112,9 @@ EOT;
|
|
105 |
continue;
|
106 |
}
|
107 |
|
|
|
108 |
if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
|
109 |
-
|
110 |
continue;
|
111 |
}
|
112 |
|
@@ -115,6 +123,16 @@ EOT;
|
|
115 |
continue;
|
116 |
}
|
117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
|
119 |
$dumpTree = true;
|
120 |
continue;
|
@@ -142,23 +160,13 @@ EOT;
|
|
142 |
continue;
|
143 |
}
|
144 |
|
145 |
-
|
146 |
-
$inputFile = $argv[$i];
|
147 |
-
continue;
|
148 |
-
}
|
149 |
}
|
150 |
|
151 |
|
152 |
-
if ($
|
|
|
153 |
$data = file_get_contents($inputFile);
|
154 |
-
|
155 |
-
$newWorkingDir = dirname(realpath($inputFile));
|
156 |
-
$oldWorkingDir = getcwd();
|
157 |
-
|
158 |
-
if ($oldWorkingDir !== $newWorkingDir) {
|
159 |
-
$changeDir = chdir($newWorkingDir);
|
160 |
-
$inputFile = basename($inputFile);
|
161 |
-
}
|
162 |
} else {
|
163 |
$data = '';
|
164 |
|
@@ -172,37 +180,65 @@ if ($dumpTree) {
|
|
172 |
|
173 |
print_r(json_decode(json_encode($parser->parse($data)), true));
|
174 |
|
|
|
|
|
175 |
exit();
|
176 |
}
|
177 |
|
178 |
$scss = new Compiler();
|
179 |
|
180 |
-
if ($debugInfo) {
|
181 |
-
$scss->setLineNumberStyle(Compiler::DEBUG_INFO);
|
182 |
-
}
|
183 |
-
|
184 |
-
if ($lineNumbers) {
|
185 |
-
$scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
|
186 |
-
}
|
187 |
-
|
188 |
if ($loadPaths) {
|
189 |
$scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
|
190 |
}
|
191 |
|
192 |
if ($style) {
|
193 |
-
$
|
|
|
|
|
|
|
|
|
|
|
194 |
}
|
195 |
|
|
|
|
|
|
|
196 |
if ($sourceMap) {
|
197 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
}
|
199 |
|
200 |
if ($encoding) {
|
201 |
$scss->setEncoding($encoding);
|
202 |
}
|
203 |
|
204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
|
206 |
-
if ($
|
207 |
-
|
|
|
|
|
|
|
208 |
}
|
20 |
include __DIR__ . '/../scss.inc.php';
|
21 |
|
22 |
use ScssPhp\ScssPhp\Compiler;
|
23 |
+
use ScssPhp\ScssPhp\Exception\SassException;
|
24 |
+
use ScssPhp\ScssPhp\OutputStyle;
|
25 |
use ScssPhp\ScssPhp\Parser;
|
26 |
use ScssPhp\ScssPhp\Version;
|
27 |
|
30 |
$dumpTree = false;
|
31 |
$inputFile = null;
|
32 |
$changeDir = false;
|
|
|
|
|
33 |
$encoding = false;
|
34 |
$sourceMap = false;
|
35 |
+
$embedSources = false;
|
36 |
+
$embedSourceMap = false;
|
37 |
|
38 |
/**
|
39 |
* Parse argument
|
62 |
}
|
63 |
}
|
64 |
|
65 |
+
$arguments = [];
|
66 |
+
|
67 |
for ($i = 1; $i < $argc; $i++) {
|
68 |
if ($argv[$i] === '-?' || $argv[$i] === '-h' || $argv[$i] === '--help') {
|
69 |
$exe = $argv[0];
|
70 |
|
71 |
$HELP = <<<EOT
|
72 |
+
Usage: $exe [options] [input-file] [output-file]
|
73 |
|
74 |
Options include:
|
75 |
|
76 |
--help Show this message [-h, -?]
|
77 |
--continue-on-error [deprecated] Ignored
|
78 |
+
--debug-info [deprecated] Ignored [-g]
|
79 |
+
--dump-tree [deprecated] Dump formatted parse tree [-T]
|
80 |
--iso8859-1 Use iso8859-1 encoding instead of default utf-8
|
81 |
+
--line-numbers [deprecated] Ignored [--line-comments]
|
82 |
--load-path=PATH Set import path [-I]
|
83 |
--precision=N [deprecated] Ignored. (default 10) [-p]
|
84 |
--sourcemap Create source map file
|
85 |
+
--embed-sources Embed source file contents in source maps
|
86 |
+
--embed-source-map Embed the source map contents in CSS (default if writing to stdout)
|
87 |
+
--style=FORMAT Set the output style (compressed or expanded) [-s, -t]
|
88 |
--version Print the version [-v]
|
89 |
|
90 |
EOT;
|
101 |
continue;
|
102 |
}
|
103 |
|
104 |
+
// Keep parsing it to avoid BC breaks for scripts using it
|
105 |
if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
|
106 |
+
// TODO report it as a warning ?
|
107 |
continue;
|
108 |
}
|
109 |
|
112 |
continue;
|
113 |
}
|
114 |
|
115 |
+
// Keep parsing it to avoid BC breaks for scripts using it
|
116 |
if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
|
117 |
+
// TODO report it as a warning ?
|
118 |
continue;
|
119 |
}
|
120 |
|
123 |
continue;
|
124 |
}
|
125 |
|
126 |
+
if ($argv[$i] === '--embed-sources') {
|
127 |
+
$embedSources = true;
|
128 |
+
continue;
|
129 |
+
}
|
130 |
+
|
131 |
+
if ($argv[$i] === '--embed-source-map') {
|
132 |
+
$embedSourceMap = true;
|
133 |
+
continue;
|
134 |
+
}
|
135 |
+
|
136 |
if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
|
137 |
$dumpTree = true;
|
138 |
continue;
|
160 |
continue;
|
161 |
}
|
162 |
|
163 |
+
$arguments[] = $argv[$i];
|
|
|
|
|
|
|
164 |
}
|
165 |
|
166 |
|
167 |
+
if (isset($arguments[0]) && file_exists($arguments[0])) {
|
168 |
+
$inputFile = $arguments[0];
|
169 |
$data = file_get_contents($inputFile);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
170 |
} else {
|
171 |
$data = '';
|
172 |
|
180 |
|
181 |
print_r(json_decode(json_encode($parser->parse($data)), true));
|
182 |
|
183 |
+
fwrite(STDERR, 'Warning: the --dump-tree option is deprecated. Use proper debugging tools instead.');
|
184 |
+
|
185 |
exit();
|
186 |
}
|
187 |
|
188 |
$scss = new Compiler();
|
189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
190 |
if ($loadPaths) {
|
191 |
$scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
|
192 |
}
|
193 |
|
194 |
if ($style) {
|
195 |
+
if ($style === OutputStyle::COMPRESSED || $style === OutputStyle::EXPANDED) {
|
196 |
+
$scss->setOutputStyle($style);
|
197 |
+
} else {
|
198 |
+
fwrite(STDERR, "WARNING: the $style style is deprecated.\n");
|
199 |
+
$scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
|
200 |
+
}
|
201 |
}
|
202 |
|
203 |
+
$outputFile = isset($arguments[1]) ? $arguments[1] : null;
|
204 |
+
$sourceMapFile = null;
|
205 |
+
|
206 |
if ($sourceMap) {
|
207 |
+
$sourceMapOptions = array(
|
208 |
+
'outputSourceFiles' => $embedSources,
|
209 |
+
);
|
210 |
+
if ($embedSourceMap || $outputFile === null) {
|
211 |
+
$scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
|
212 |
+
} else {
|
213 |
+
$sourceMapFile = $outputFile . '.map';
|
214 |
+
$sourceMapOptions['sourceMapWriteTo'] = $sourceMapFile;
|
215 |
+
$sourceMapOptions['sourceMapURL'] = basename($sourceMapFile);
|
216 |
+
$sourceMapOptions['sourceMapBasepath'] = getcwd();
|
217 |
+
$sourceMapOptions['sourceMapFilename'] = basename($outputFile);
|
218 |
+
|
219 |
+
$scss->setSourceMap(Compiler::SOURCE_MAP_FILE);
|
220 |
+
}
|
221 |
+
|
222 |
+
$scss->setSourceMapOptions($sourceMapOptions);
|
223 |
}
|
224 |
|
225 |
if ($encoding) {
|
226 |
$scss->setEncoding($encoding);
|
227 |
}
|
228 |
|
229 |
+
try {
|
230 |
+
$result = $scss->compileString($data, $inputFile);
|
231 |
+
} catch (SassException $e) {
|
232 |
+
fwrite(STDERR, 'Error: '.$e->getMessage()."\n");
|
233 |
+
exit(1);
|
234 |
+
}
|
235 |
+
|
236 |
+
if ($outputFile) {
|
237 |
+
file_put_contents($outputFile, $result->getCss());
|
238 |
|
239 |
+
if ($sourceMapFile !== null && $result->getSourceMap() !== null) {
|
240 |
+
file_put_contents($sourceMapFile, $result->getSourceMap());
|
241 |
+
}
|
242 |
+
} else {
|
243 |
+
echo $result->getCss();
|
244 |
}
|
scssphp/composer.json
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "scssphp/scssphp",
|
3 |
+
"type": "library",
|
4 |
+
"description": "scssphp is a compiler for SCSS written in PHP.",
|
5 |
+
"keywords": ["css", "stylesheet", "scss", "sass", "less"],
|
6 |
+
"homepage": "http://scssphp.github.io/scssphp/",
|
7 |
+
"license": [
|
8 |
+
"MIT"
|
9 |
+
],
|
10 |
+
"authors": [
|
11 |
+
{
|
12 |
+
"name": "Anthon Pang",
|
13 |
+
"email": "apang@softwaredevelopment.ca",
|
14 |
+
"homepage": "https://github.com/robocoder"
|
15 |
+
},
|
16 |
+
{
|
17 |
+
"name": "Cédric Morin",
|
18 |
+
"email": "cedric@yterium.com",
|
19 |
+
"homepage": "https://github.com/Cerdic"
|
20 |
+
}
|
21 |
+
],
|
22 |
+
"autoload": {
|
23 |
+
"psr-4": { "ScssPhp\\ScssPhp\\": "src/" }
|
24 |
+
},
|
25 |
+
"autoload-dev": {
|
26 |
+
"psr-4": { "ScssPhp\\ScssPhp\\Tests\\": "tests/" }
|
27 |
+
},
|
28 |
+
"require": {
|
29 |
+
"php": ">=5.6.0",
|
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 |
+
"twbs/bootstrap": "~5.0",
|
44 |
+
"twbs/bootstrap4": "4.6.0",
|
45 |
+
"zurb/foundation": "~6.5"
|
46 |
+
},
|
47 |
+
"repositories": [
|
48 |
+
{
|
49 |
+
"type": "package",
|
50 |
+
"package": {
|
51 |
+
"name": "sass/sass-spec",
|
52 |
+
"version": "2021.05.10",
|
53 |
+
"source": {
|
54 |
+
"type": "git",
|
55 |
+
"url": "https://github.com/sass/sass-spec.git",
|
56 |
+
"reference": "b9bf24a936528f333fb30ee59ca550c6da551c11"
|
57 |
+
},
|
58 |
+
"dist": {
|
59 |
+
"type": "zip",
|
60 |
+
"url": "https://api.github.com/repos/sass/sass-spec/zipball/b9bf24a936528f333fb30ee59ca550c6da551c11",
|
61 |
+
"reference": "b9bf24a936528f333fb30ee59ca550c6da551c11",
|
62 |
+
"shasum": ""
|
63 |
+
}
|
64 |
+
}
|
65 |
+
},
|
66 |
+
{
|
67 |
+
"type": "package",
|
68 |
+
"package": {
|
69 |
+
"name": "twbs/bootstrap4",
|
70 |
+
"version": "v4.6.0",
|
71 |
+
"source": {
|
72 |
+
"type": "git",
|
73 |
+
"url": "https://github.com/twbs/bootstrap.git",
|
74 |
+
"reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2"
|
75 |
+
},
|
76 |
+
"dist": {
|
77 |
+
"type": "zip",
|
78 |
+
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/6ffb0b48e455430f8a5359ed689ad64c1143fac2",
|
79 |
+
"reference": "6ffb0b48e455430f8a5359ed689ad64c1143fac2",
|
80 |
+
"shasum": ""
|
81 |
+
}
|
82 |
+
}
|
83 |
+
}
|
84 |
+
],
|
85 |
+
"bin": ["bin/pscss"],
|
86 |
+
"config": {
|
87 |
+
"sort-packages": true
|
88 |
+
}
|
89 |
+
}
|
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>
|
scssphp/scss.inc.php
CHANGED
@@ -4,33 +4,18 @@ 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 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
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 |
}
|
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 |
}
|
scssphp/src/Base/Range.php
CHANGED
@@ -16,10 +16,19 @@ namespace ScssPhp\ScssPhp\Base;
|
|
16 |
* Range
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class Range
|
21 |
{
|
|
|
|
|
|
|
22 |
public $first;
|
|
|
|
|
|
|
|
|
23 |
public $last;
|
24 |
|
25 |
/**
|
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 |
/**
|
scssphp/src/Block.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp;
|
|
16 |
* Block
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class Block
|
21 |
{
|
@@ -50,7 +52,7 @@ class Block
|
|
50 |
public $sourceColumn;
|
51 |
|
52 |
/**
|
53 |
-
* @var array
|
54 |
*/
|
55 |
public $selectors;
|
56 |
|
@@ -65,7 +67,7 @@ class Block
|
|
65 |
public $children;
|
66 |
|
67 |
/**
|
68 |
-
* @var \ScssPhp\ScssPhp\Block
|
69 |
*/
|
70 |
public $selfParent;
|
71 |
}
|
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 |
}
|
scssphp/src/Cache.php
CHANGED
@@ -13,6 +13,7 @@
|
|
13 |
namespace ScssPhp\ScssPhp;
|
14 |
|
15 |
use Exception;
|
|
|
16 |
|
17 |
/**
|
18 |
* The scss cache manager.
|
@@ -29,30 +30,54 @@ use Exception;
|
|
29 |
* SCSS cache
|
30 |
*
|
31 |
* @author Cedric Morin <cedric@yterium.com>
|
|
|
|
|
32 |
*/
|
33 |
class Cache
|
34 |
{
|
35 |
const CACHE_VERSION = 1;
|
36 |
|
37 |
-
|
|
|
|
|
|
|
|
|
38 |
public static $cacheDir = false;
|
39 |
|
40 |
-
|
|
|
|
|
|
|
|
|
41 |
public static $prefix = 'scssphp_';
|
42 |
|
43 |
-
|
|
|
|
|
|
|
|
|
44 |
public static $forceRefresh = false;
|
45 |
|
46 |
-
|
|
|
|
|
|
|
|
|
47 |
public static $gcLifetime = 604800;
|
48 |
|
49 |
-
|
|
|
|
|
|
|
|
|
50 |
protected static $refreshed = [];
|
51 |
|
52 |
/**
|
53 |
* Constructor
|
54 |
*
|
55 |
* @param array $options
|
|
|
|
|
56 |
*/
|
57 |
public function __construct($options)
|
58 |
{
|
@@ -84,10 +109,10 @@ class Cache
|
|
84 |
* Get the cached result of $operation on $what,
|
85 |
* which is known as dependant from the content of $options
|
86 |
*
|
87 |
-
* @param string
|
88 |
-
* @param mixed
|
89 |
-
* @param array
|
90 |
-
* @param
|
91 |
*
|
92 |
* @return mixed
|
93 |
*
|
@@ -127,6 +152,8 @@ class Cache
|
|
127 |
* @param mixed $what
|
128 |
* @param mixed $value
|
129 |
* @param array $options
|
|
|
|
|
130 |
*/
|
131 |
public function setCache($operation, $what, $value, $options = [])
|
132 |
{
|
@@ -156,6 +183,7 @@ class Cache
|
|
156 |
{
|
157 |
$t = [
|
158 |
'version' => self::CACHE_VERSION,
|
|
|
159 |
'operation' => $operation,
|
160 |
'what' => $what,
|
161 |
'options' => $options
|
@@ -172,6 +200,8 @@ class Cache
|
|
172 |
/**
|
173 |
* Check that the cache dir exists and is writeable
|
174 |
*
|
|
|
|
|
175 |
* @throws \Exception
|
176 |
*/
|
177 |
public static function checkCacheDir()
|
@@ -190,6 +220,8 @@ class Cache
|
|
190 |
|
191 |
/**
|
192 |
* Delete unused cached files
|
|
|
|
|
193 |
*/
|
194 |
public static function cleanCache()
|
195 |
{
|
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 |
*
|
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()
|
220 |
|
221 |
/**
|
222 |
* Delete unused cached files
|
223 |
+
*
|
224 |
+
* @return void
|
225 |
*/
|
226 |
public static function cleanCache()
|
227 |
{
|
scssphp/src/Colors.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp;
|
|
16 |
* CSS Colors
|
17 |
*
|
18 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class Colors
|
21 |
{
|
@@ -24,13 +26,13 @@ class Colors
|
|
24 |
*
|
25 |
* @see http://www.w3.org/TR/css3-color
|
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',
|
36 |
'beige' => '245,245,220',
|
@@ -75,8 +77,8 @@ class Colors
|
|
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',
|
@@ -183,7 +185,7 @@ class Colors
|
|
183 |
*
|
184 |
* @param string $colorName
|
185 |
*
|
186 |
-
* @return
|
187 |
*/
|
188 |
public static function colorNameToRGBa($colorName)
|
189 |
{
|
@@ -205,7 +207,7 @@ class Colors
|
|
205 |
* @param integer $r
|
206 |
* @param integer $g
|
207 |
* @param integer $b
|
208 |
-
* @param integer $a
|
209 |
*
|
210 |
* @return string|null
|
211 |
*/
|
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',
|
77 |
'firebrick' => '178,34,34',
|
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',
|
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 |
*/
|
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 |
+
}
|
scssphp/src/Compiler.php
CHANGED
@@ -13,17 +13,20 @@
|
|
13 |
namespace ScssPhp\ScssPhp;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Base\Range;
|
16 |
-
use ScssPhp\ScssPhp\
|
17 |
-
use ScssPhp\ScssPhp\Cache;
|
18 |
-
use ScssPhp\ScssPhp\Colors;
|
19 |
use ScssPhp\ScssPhp\Compiler\Environment;
|
20 |
use ScssPhp\ScssPhp\Exception\CompilerException;
|
|
|
|
|
|
|
|
|
|
|
21 |
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
22 |
-
use ScssPhp\ScssPhp\
|
|
|
|
|
23 |
use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
|
24 |
-
use ScssPhp\ScssPhp\
|
25 |
-
use ScssPhp\ScssPhp\Parser;
|
26 |
-
use ScssPhp\ScssPhp\Util;
|
27 |
|
28 |
/**
|
29 |
* The scss compiler and parser.
|
@@ -56,15 +59,35 @@ use ScssPhp\ScssPhp\Util;
|
|
56 |
* SCSS compiler
|
57 |
*
|
58 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
59 |
*/
|
60 |
class Compiler
|
61 |
{
|
|
|
|
|
|
|
62 |
const LINE_COMMENTS = 1;
|
|
|
|
|
|
|
63 |
const DEBUG_INFO = 2;
|
64 |
|
|
|
|
|
|
|
65 |
const WITH_RULE = 1;
|
|
|
|
|
|
|
66 |
const WITH_MEDIA = 2;
|
|
|
|
|
|
|
67 |
const WITH_SUPPORTS = 4;
|
|
|
|
|
|
|
68 |
const WITH_ALL = 7;
|
69 |
|
70 |
const SOURCE_MAP_NONE = 0;
|
@@ -72,7 +95,7 @@ class Compiler
|
|
72 |
const SOURCE_MAP_FILE = 2;
|
73 |
|
74 |
/**
|
75 |
-
* @var array
|
76 |
*/
|
77 |
protected static $operatorNames = [
|
78 |
'+' => 'add',
|
@@ -88,11 +111,10 @@ class Compiler
|
|
88 |
|
89 |
'<=' => 'lte',
|
90 |
'>=' => 'gte',
|
91 |
-
'<=>' => 'cmp',
|
92 |
];
|
93 |
|
94 |
/**
|
95 |
-
* @var array
|
96 |
*/
|
97 |
protected static $namespaces = [
|
98 |
'special' => '%',
|
@@ -102,7 +124,9 @@ class Compiler
|
|
102 |
|
103 |
public static $true = [Type::T_KEYWORD, 'true'];
|
104 |
public static $false = [Type::T_KEYWORD, 'false'];
|
|
|
105 |
public static $NaN = [Type::T_KEYWORD, 'NaN'];
|
|
|
106 |
public static $Infinity = [Type::T_KEYWORD, 'Infinity'];
|
107 |
public static $null = [Type::T_NULL];
|
108 |
public static $nullString = [Type::T_STRING, '', []];
|
@@ -114,79 +138,220 @@ class Compiler
|
|
114 |
public static $with = [Type::T_KEYWORD, 'with'];
|
115 |
public static $without = [Type::T_KEYWORD, 'without'];
|
116 |
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
protected $importCache = [];
|
|
|
|
|
|
|
|
|
119 |
protected $importedFiles = [];
|
|
|
|
|
|
|
|
|
|
|
120 |
protected $userFunctions = [];
|
|
|
|
|
|
|
121 |
protected $registeredVars = [];
|
|
|
|
|
|
|
122 |
protected $registeredFeatures = [
|
123 |
'extend-selector-pseudoclass' => false,
|
124 |
'at-error' => true,
|
125 |
-
'units-level-3' =>
|
126 |
'global-variable-shadowing' => false,
|
127 |
];
|
128 |
|
|
|
|
|
|
|
129 |
protected $encoding = null;
|
|
|
|
|
|
|
|
|
130 |
protected $lineNumberStyle = null;
|
131 |
|
|
|
|
|
|
|
|
|
132 |
protected $sourceMap = self::SOURCE_MAP_NONE;
|
|
|
|
|
|
|
|
|
|
|
133 |
protected $sourceMapOptions = [];
|
134 |
|
135 |
/**
|
136 |
* @var string|\ScssPhp\ScssPhp\Formatter
|
137 |
*/
|
138 |
-
protected $formatter =
|
139 |
|
|
|
|
|
|
|
140 |
protected $rootEnv;
|
|
|
|
|
|
|
141 |
protected $rootBlock;
|
142 |
|
143 |
/**
|
144 |
* @var \ScssPhp\ScssPhp\Compiler\Environment
|
145 |
*/
|
146 |
protected $env;
|
|
|
|
|
|
|
147 |
protected $scope;
|
|
|
|
|
|
|
148 |
protected $storeEnv;
|
|
|
|
|
|
|
149 |
protected $charsetSeen;
|
|
|
|
|
|
|
150 |
protected $sourceNames;
|
151 |
|
|
|
|
|
|
|
152 |
protected $cache;
|
153 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
protected $indentLevel;
|
|
|
|
|
|
|
155 |
protected $extends;
|
|
|
|
|
|
|
156 |
protected $extendsMap;
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
protected $parser;
|
|
|
|
|
|
|
159 |
protected $sourceIndex;
|
|
|
|
|
|
|
160 |
protected $sourceLine;
|
|
|
|
|
|
|
161 |
protected $sourceColumn;
|
162 |
-
|
|
|
|
|
163 |
protected $shouldEvaluate;
|
|
|
|
|
|
|
|
|
164 |
protected $ignoreErrors;
|
|
|
|
|
|
|
165 |
protected $ignoreCallStackMessage = false;
|
166 |
|
|
|
|
|
|
|
167 |
protected $callStack = [];
|
168 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
/**
|
170 |
* Constructor
|
171 |
*
|
172 |
* @param array|null $cacheOptions
|
|
|
173 |
*/
|
174 |
public function __construct($cacheOptions = null)
|
175 |
{
|
176 |
-
$this->parsedFiles = [];
|
177 |
$this->sourceNames = [];
|
178 |
|
179 |
if ($cacheOptions) {
|
180 |
$this->cache = new Cache($cacheOptions);
|
|
|
|
|
|
|
181 |
}
|
182 |
|
183 |
-
$this->
|
184 |
}
|
185 |
|
186 |
/**
|
187 |
* Get compiler options
|
188 |
*
|
189 |
-
* @return array
|
|
|
|
|
190 |
*/
|
191 |
public function getCompileOptions()
|
192 |
{
|
@@ -198,53 +363,96 @@ class Compiler
|
|
198 |
'sourceMap' => serialize($this->sourceMap),
|
199 |
'sourceMapOptions' => $this->sourceMapOptions,
|
200 |
'formatter' => $this->formatter,
|
|
|
201 |
];
|
202 |
|
203 |
return $options;
|
204 |
}
|
205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
/**
|
207 |
* Set an alternative error output stream, for testing purpose only
|
208 |
*
|
209 |
* @param resource $handle
|
|
|
|
|
|
|
|
|
210 |
*/
|
211 |
public function setErrorOuput($handle)
|
212 |
{
|
213 |
-
|
|
|
|
|
214 |
}
|
215 |
|
216 |
/**
|
217 |
* Compile scss
|
218 |
*
|
219 |
-
* @
|
220 |
-
*
|
221 |
-
* @param string $code
|
222 |
-
* @param string $path
|
223 |
*
|
224 |
* @return string
|
|
|
|
|
|
|
|
|
225 |
*/
|
226 |
public function compile($code, $path = null)
|
227 |
{
|
228 |
-
|
229 |
-
$cacheKey = ($path ? $path : '(stdin)') . ':' . md5($code);
|
230 |
-
$compileOptions = $this->getCompileOptions();
|
231 |
-
$cache = $this->cache->getCache('compile', $cacheKey, $compileOptions);
|
232 |
|
233 |
-
|
234 |
-
// check if any dependency file changed before accepting the cache
|
235 |
-
foreach ($cache['dependencies'] as $file => $mtime) {
|
236 |
-
if (! is_file($file) || filemtime($file) !== $mtime) {
|
237 |
-
unset($cache);
|
238 |
-
break;
|
239 |
-
}
|
240 |
-
}
|
241 |
|
242 |
-
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
245 |
}
|
246 |
}
|
247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
$this->indentLevel = -1;
|
250 |
$this->extends = [];
|
@@ -258,65 +466,149 @@ class Compiler
|
|
258 |
$this->charsetSeen = null;
|
259 |
$this->shouldEvaluate = null;
|
260 |
$this->ignoreCallStackMessage = false;
|
|
|
|
|
|
|
261 |
|
262 |
-
$
|
263 |
-
|
264 |
-
|
|
|
|
|
|
|
|
|
|
|
265 |
|
266 |
-
|
267 |
-
|
268 |
-
|
|
|
269 |
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
|
274 |
-
|
|
|
|
|
|
|
275 |
|
276 |
-
|
277 |
-
|
278 |
-
$
|
279 |
-
$this->
|
280 |
-
}
|
281 |
-
|
282 |
}
|
283 |
-
}
|
284 |
|
285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
|
287 |
-
|
288 |
-
$sourceMap = $sourceMapGenerator->generateJson();
|
289 |
-
$sourceMapUrl = null;
|
290 |
|
291 |
-
|
292 |
-
case self::SOURCE_MAP_INLINE:
|
293 |
-
$sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
|
294 |
-
break;
|
295 |
|
296 |
-
|
297 |
-
|
298 |
-
|
|
|
|
|
299 |
}
|
300 |
|
301 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
}
|
303 |
|
|
|
|
|
304 |
if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
|
305 |
-
$
|
306 |
-
|
307 |
-
'out' => &$out,
|
308 |
-
];
|
309 |
|
310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
}
|
312 |
|
313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
}
|
315 |
|
316 |
/**
|
317 |
* Instantiate parser
|
318 |
*
|
319 |
-
* @param string $path
|
320 |
*
|
321 |
* @return \ScssPhp\ScssPhp\Parser
|
322 |
*/
|
@@ -329,11 +621,11 @@ class Compiler
|
|
329 |
// Otherwise, the CSS will be rendered as-is. It can even be extended!
|
330 |
$cssOnly = false;
|
331 |
|
332 |
-
if (substr($path,
|
333 |
$cssOnly = true;
|
334 |
}
|
335 |
|
336 |
-
$parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly);
|
337 |
|
338 |
$this->sourceNames[] = $path;
|
339 |
$this->addParsedFile($path);
|
@@ -366,6 +658,8 @@ class Compiler
|
|
366 |
* @param array $target
|
367 |
* @param array $origin
|
368 |
* @param array|null $block
|
|
|
|
|
369 |
*/
|
370 |
protected function pushExtends($target, $origin, $block)
|
371 |
{
|
@@ -384,8 +678,8 @@ class Compiler
|
|
384 |
/**
|
385 |
* Make output block
|
386 |
*
|
387 |
-
* @param string
|
388 |
-
* @param
|
389 |
*
|
390 |
* @return \ScssPhp\ScssPhp\Formatter\OutputBlock
|
391 |
*/
|
@@ -416,6 +710,8 @@ class Compiler
|
|
416 |
* Compile root
|
417 |
*
|
418 |
* @param \ScssPhp\ScssPhp\Block $rootBlock
|
|
|
|
|
419 |
*/
|
420 |
protected function compileRoot(Block $rootBlock)
|
421 |
{
|
@@ -428,6 +724,8 @@ class Compiler
|
|
428 |
|
429 |
/**
|
430 |
* Report missing selectors
|
|
|
|
|
431 |
*/
|
432 |
protected function missingSelectors()
|
433 |
{
|
@@ -456,6 +754,8 @@ class Compiler
|
|
456 |
*
|
457 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
458 |
* @param string $parentKey
|
|
|
|
|
459 |
*/
|
460 |
protected function flattenSelectors(OutputBlock $block, $parentKey = null)
|
461 |
{
|
@@ -511,7 +811,7 @@ class Compiler
|
|
511 |
}
|
512 |
|
513 |
/**
|
514 |
-
* Glue parts of :not( or :nth-child( ... that are in general
|
515 |
*
|
516 |
* @param array $parts
|
517 |
*
|
@@ -552,6 +852,8 @@ class Compiler
|
|
552 |
* @param array $out
|
553 |
* @param integer $from
|
554 |
* @param boolean $initial
|
|
|
|
|
555 |
*/
|
556 |
protected function matchExtends($selector, &$out, $from = 0, $initial = true)
|
557 |
{
|
@@ -705,6 +1007,8 @@ class Compiler
|
|
705 |
*
|
706 |
* @param array $out
|
707 |
* @param array $extended
|
|
|
|
|
708 |
*/
|
709 |
protected function pushOrMergeExtentedSelector(&$out, $extended)
|
710 |
{
|
@@ -796,7 +1100,7 @@ class Compiler
|
|
796 |
$buffer = $matches[2];
|
797 |
$parser = $this->parserFactory(__METHOD__);
|
798 |
|
799 |
-
if ($parser->parseSelector($buffer, $subSelectors)) {
|
800 |
foreach ($subSelectors as $ksub => $subSelector) {
|
801 |
$subExtended = [];
|
802 |
$this->matchExtends($subSelector, $subExtended, 0, false);
|
@@ -956,6 +1260,8 @@ class Compiler
|
|
956 |
* Compile media
|
957 |
*
|
958 |
* @param \ScssPhp\ScssPhp\Block $media
|
|
|
|
|
959 |
*/
|
960 |
protected function compileMedia(Block $media)
|
961 |
{
|
@@ -963,7 +1269,7 @@ class Compiler
|
|
963 |
|
964 |
$mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
|
965 |
|
966 |
-
if (! empty($mediaQueries)
|
967 |
$previousScope = $this->scope;
|
968 |
$parentScope = $this->mediaParent($this->scope);
|
969 |
|
@@ -1003,30 +1309,6 @@ class Compiler
|
|
1003 |
$wrapped->children = $media->children;
|
1004 |
|
1005 |
$media->children = [[Type::T_BLOCK, $wrapped]];
|
1006 |
-
|
1007 |
-
if (isset($this->lineNumberStyle)) {
|
1008 |
-
$annotation = $this->makeOutputBlock(Type::T_COMMENT);
|
1009 |
-
$annotation->depth = 0;
|
1010 |
-
|
1011 |
-
$file = $this->sourceNames[$media->sourceIndex];
|
1012 |
-
$line = $media->sourceLine;
|
1013 |
-
|
1014 |
-
switch ($this->lineNumberStyle) {
|
1015 |
-
case static::LINE_COMMENTS:
|
1016 |
-
$annotation->lines[] = '/* line ' . $line
|
1017 |
-
. ($file ? ', ' . $file : '')
|
1018 |
-
. ' */';
|
1019 |
-
break;
|
1020 |
-
|
1021 |
-
case static::DEBUG_INFO:
|
1022 |
-
$annotation->lines[] = '@media -sass-debug-info{'
|
1023 |
-
. ($file ? 'filename{font-family:"' . $file . '"}' : '')
|
1024 |
-
. 'line{font-family:' . $line . '}}';
|
1025 |
-
break;
|
1026 |
-
}
|
1027 |
-
|
1028 |
-
$this->scope->children[] = $annotation;
|
1029 |
-
}
|
1030 |
}
|
1031 |
|
1032 |
$this->compileChildrenNoReturn($media->children, $this->scope);
|
@@ -1060,8 +1342,10 @@ class Compiler
|
|
1060 |
/**
|
1061 |
* Compile directive
|
1062 |
*
|
1063 |
-
* @param \ScssPhp\ScssPhp\Block|array $
|
1064 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
|
|
|
|
1065 |
*/
|
1066 |
protected function compileDirective($directive, OutputBlock $out)
|
1067 |
{
|
@@ -1103,7 +1387,7 @@ class Compiler
|
|
1103 |
* directive names can include some interpolation
|
1104 |
*
|
1105 |
* @param string|array $directiveName
|
1106 |
-
* @return
|
1107 |
* @throws CompilerException
|
1108 |
*/
|
1109 |
protected function compileDirectiveName($directiveName)
|
@@ -1119,6 +1403,8 @@ class Compiler
|
|
1119 |
* Compile at-root
|
1120 |
*
|
1121 |
* @param \ScssPhp\ScssPhp\Block $block
|
|
|
|
|
1122 |
*/
|
1123 |
protected function compileAtRoot(Block $block)
|
1124 |
{
|
@@ -1144,9 +1430,10 @@ class Compiler
|
|
1144 |
}
|
1145 |
|
1146 |
$selfParent = $block->selfParent;
|
|
|
1147 |
|
1148 |
if (
|
1149 |
-
! $
|
1150 |
isset($block->parent) && $block->parent &&
|
1151 |
isset($block->parent->selectors) && $block->parent->selectors
|
1152 |
) {
|
@@ -1175,19 +1462,19 @@ class Compiler
|
|
1175 |
* @param array $with
|
1176 |
* @param array $without
|
1177 |
*
|
1178 |
-
* @return
|
1179 |
*/
|
1180 |
protected function filterScopeWithWithout($scope, $with, $without)
|
1181 |
{
|
1182 |
$filteredScopes = [];
|
1183 |
$childStash = [];
|
1184 |
|
1185 |
-
if ($scope->type ===
|
1186 |
return $scope;
|
1187 |
}
|
1188 |
|
1189 |
// start from the root
|
1190 |
-
while ($scope->parent && $scope->parent->type !==
|
1191 |
array_unshift($childStash, $scope);
|
1192 |
$scope = $scope->parent;
|
1193 |
}
|
@@ -1248,7 +1535,7 @@ class Compiler
|
|
1248 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
|
1249 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
|
1250 |
*
|
1251 |
-
* @return
|
1252 |
*/
|
1253 |
protected function completeScope($scope, $previousScope)
|
1254 |
{
|
@@ -1315,7 +1602,7 @@ class Compiler
|
|
1315 |
}
|
1316 |
}
|
1317 |
|
1318 |
-
if ($this->
|
1319 |
$without = []; // cancel the default
|
1320 |
$list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
|
1321 |
|
@@ -1326,7 +1613,7 @@ class Compiler
|
|
1326 |
}
|
1327 |
}
|
1328 |
|
1329 |
-
if ($this->
|
1330 |
$without = []; // cancel the default
|
1331 |
$list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
|
1332 |
|
@@ -1344,11 +1631,13 @@ class Compiler
|
|
1344 |
/**
|
1345 |
* Filter env stack
|
1346 |
*
|
1347 |
-
* @param
|
1348 |
* @param array $with
|
1349 |
* @param array $without
|
1350 |
*
|
1351 |
-
* @return
|
|
|
|
|
1352 |
*/
|
1353 |
protected function filterWithWithout($envs, $with, $without)
|
1354 |
{
|
@@ -1403,7 +1692,7 @@ class Compiler
|
|
1403 |
$s = reset($s);
|
1404 |
}
|
1405 |
|
1406 |
-
if (\is_object($s) && $s instanceof
|
1407 |
return $this->testWithWithout('keyframes', $with, $without);
|
1408 |
}
|
1409 |
}
|
@@ -1440,7 +1729,9 @@ class Compiler
|
|
1440 |
* Compile keyframe block
|
1441 |
*
|
1442 |
* @param \ScssPhp\ScssPhp\Block $block
|
1443 |
-
* @param
|
|
|
|
|
1444 |
*/
|
1445 |
protected function compileKeyframeBlock(Block $block, $selectors)
|
1446 |
{
|
@@ -1469,6 +1760,8 @@ class Compiler
|
|
1469 |
*
|
1470 |
* @param \ScssPhp\ScssPhp\Block $block
|
1471 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
|
|
|
|
1472 |
*/
|
1473 |
protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
|
1474 |
{
|
@@ -1502,7 +1795,9 @@ class Compiler
|
|
1502 |
* Compile nested block
|
1503 |
*
|
1504 |
* @param \ScssPhp\ScssPhp\Block $block
|
1505 |
-
* @param
|
|
|
|
|
1506 |
*/
|
1507 |
protected function compileNestedBlock(Block $block, $selectors)
|
1508 |
{
|
@@ -1564,6 +1859,8 @@ class Compiler
|
|
1564 |
* @see Compiler::compileChild()
|
1565 |
*
|
1566 |
* @param \ScssPhp\ScssPhp\Block $block
|
|
|
|
|
1567 |
*/
|
1568 |
protected function compileBlock(Block $block)
|
1569 |
{
|
@@ -1572,30 +1869,6 @@ class Compiler
|
|
1572 |
|
1573 |
$out = $this->makeOutputBlock(null);
|
1574 |
|
1575 |
-
if (isset($this->lineNumberStyle) && \count($env->selectors) && \count($block->children)) {
|
1576 |
-
$annotation = $this->makeOutputBlock(Type::T_COMMENT);
|
1577 |
-
$annotation->depth = 0;
|
1578 |
-
|
1579 |
-
$file = $this->sourceNames[$block->sourceIndex];
|
1580 |
-
$line = $block->sourceLine;
|
1581 |
-
|
1582 |
-
switch ($this->lineNumberStyle) {
|
1583 |
-
case static::LINE_COMMENTS:
|
1584 |
-
$annotation->lines[] = '/* line ' . $line
|
1585 |
-
. ($file ? ', ' . $file : '')
|
1586 |
-
. ' */';
|
1587 |
-
break;
|
1588 |
-
|
1589 |
-
case static::DEBUG_INFO:
|
1590 |
-
$annotation->lines[] = '@media -sass-debug-info{'
|
1591 |
-
. ($file ? 'filename{font-family:"' . $file . '"}' : '')
|
1592 |
-
. 'line{font-family:' . $line . '}}';
|
1593 |
-
break;
|
1594 |
-
}
|
1595 |
-
|
1596 |
-
$this->scope->children[] = $annotation;
|
1597 |
-
}
|
1598 |
-
|
1599 |
$this->scope->children[] = $out;
|
1600 |
|
1601 |
if (\count($block->children)) {
|
@@ -1627,7 +1900,7 @@ class Compiler
|
|
1627 |
* @param array $value
|
1628 |
* @param boolean $pushEnv
|
1629 |
*
|
1630 |
-
* @return
|
1631 |
*/
|
1632 |
protected function compileCommentValue($value, $pushEnv = false)
|
1633 |
{
|
@@ -1638,17 +1911,16 @@ class Compiler
|
|
1638 |
$this->pushEnv();
|
1639 |
}
|
1640 |
|
1641 |
-
$ignoreCallStackMessage = $this->ignoreCallStackMessage;
|
1642 |
-
$this->ignoreCallStackMessage = true;
|
1643 |
-
|
1644 |
try {
|
1645 |
$c = $this->compileValue($value[2]);
|
1646 |
-
} catch (
|
|
|
|
|
|
|
|
|
1647 |
// ignore error in comment compilation which are only interpolation
|
1648 |
}
|
1649 |
|
1650 |
-
$this->ignoreCallStackMessage = $ignoreCallStackMessage;
|
1651 |
-
|
1652 |
if ($pushEnv) {
|
1653 |
$this->popEnv();
|
1654 |
}
|
@@ -1661,6 +1933,8 @@ class Compiler
|
|
1661 |
* Compile root level comment
|
1662 |
*
|
1663 |
* @param array $block
|
|
|
|
|
1664 |
*/
|
1665 |
protected function compileComment($block)
|
1666 |
{
|
@@ -1689,7 +1963,13 @@ class Compiler
|
|
1689 |
$buffer = $this->collapseSelectors($selectors);
|
1690 |
$parser = $this->parserFactory(__METHOD__);
|
1691 |
|
1692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
1693 |
$selectors = array_map([$this, 'evalSelector'], $newSelectors);
|
1694 |
}
|
1695 |
}
|
@@ -1722,8 +2002,8 @@ class Compiler
|
|
1722 |
if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
|
1723 |
$p = $this->compileValue($p);
|
1724 |
|
1725 |
-
// force re-evaluation
|
1726 |
-
if (
|
1727 |
$this->shouldEvaluate = true;
|
1728 |
}
|
1729 |
} elseif (
|
@@ -1741,20 +2021,16 @@ class Compiler
|
|
1741 |
/**
|
1742 |
* Collapse selectors
|
1743 |
*
|
1744 |
-
* @param array
|
1745 |
-
* @param boolean $selectorFormat
|
1746 |
-
* if false return a collapsed string
|
1747 |
-
* if true return an array description of a structured selector
|
1748 |
*
|
1749 |
* @return string
|
1750 |
*/
|
1751 |
-
protected function collapseSelectors($selectors
|
1752 |
{
|
1753 |
$parts = [];
|
1754 |
|
1755 |
foreach ($selectors as $selector) {
|
1756 |
$output = [];
|
1757 |
-
$glueNext = false;
|
1758 |
|
1759 |
foreach ($selector as $node) {
|
1760 |
$compound = '';
|
@@ -1766,48 +2042,71 @@ class Compiler
|
|
1766 |
}
|
1767 |
);
|
1768 |
|
1769 |
-
|
1770 |
-
if (\count($output)) {
|
1771 |
-
$output[\count($output) - 1] .= ' ' . $compound;
|
1772 |
-
} else {
|
1773 |
-
$output[] = $compound;
|
1774 |
-
}
|
1775 |
-
|
1776 |
-
$glueNext = true;
|
1777 |
-
} elseif ($glueNext) {
|
1778 |
-
$output[\count($output) - 1] .= ' ' . $compound;
|
1779 |
-
$glueNext = false;
|
1780 |
-
} else {
|
1781 |
-
$output[] = $compound;
|
1782 |
-
}
|
1783 |
}
|
1784 |
|
1785 |
-
|
1786 |
-
|
1787 |
-
$o = [Type::T_STRING, '', [$o]];
|
1788 |
-
}
|
1789 |
|
1790 |
-
|
1791 |
-
|
1792 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1793 |
}
|
1794 |
|
1795 |
-
$
|
1796 |
-
|
|
|
1797 |
|
1798 |
-
|
1799 |
-
$parts = [Type::T_LIST, ',', $parts];
|
1800 |
-
} else {
|
1801 |
-
$parts = implode(', ', $parts);
|
1802 |
}
|
1803 |
|
1804 |
-
return $parts;
|
1805 |
}
|
1806 |
|
1807 |
/**
|
1808 |
* Parse down the selector and revert [self] to "&" before a reparsing
|
1809 |
*
|
1810 |
-
* @param array
|
|
|
1811 |
*
|
1812 |
* @return array
|
1813 |
*/
|
@@ -1935,6 +2234,11 @@ class Compiler
|
|
1935 |
return false;
|
1936 |
}
|
1937 |
|
|
|
|
|
|
|
|
|
|
|
1938 |
protected function pushCallStack($name = '')
|
1939 |
{
|
1940 |
$this->callStack[] = [
|
@@ -1954,6 +2258,9 @@ class Compiler
|
|
1954 |
}
|
1955 |
}
|
1956 |
|
|
|
|
|
|
|
1957 |
protected function popCallStack()
|
1958 |
{
|
1959 |
array_pop($this->callStack);
|
@@ -1966,7 +2273,7 @@ class Compiler
|
|
1966 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
1967 |
* @param string $traceName
|
1968 |
*
|
1969 |
-
* @return array|null
|
1970 |
*/
|
1971 |
protected function compileChildren($stms, OutputBlock $out, $traceName = '')
|
1972 |
{
|
@@ -1988,13 +2295,15 @@ class Compiler
|
|
1988 |
}
|
1989 |
|
1990 |
/**
|
1991 |
-
* Compile children and throw exception if unexpected
|
1992 |
*
|
1993 |
* @param array $stms
|
1994 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
1995 |
* @param \ScssPhp\ScssPhp\Block $selfParent
|
1996 |
* @param string $traceName
|
1997 |
*
|
|
|
|
|
1998 |
* @throws \Exception
|
1999 |
*/
|
2000 |
protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
|
@@ -2006,7 +2315,7 @@ class Compiler
|
|
2006 |
$stm[1]->selfParent = $selfParent;
|
2007 |
$ret = $this->compileChild($stm, $out);
|
2008 |
$stm[1]->selfParent = null;
|
2009 |
-
} elseif ($selfParent && \in_array($stm[0], [
|
2010 |
$stm['selfParent'] = $selfParent;
|
2011 |
$ret = $this->compileChild($stm, $out);
|
2012 |
unset($stm['selfParent']);
|
@@ -2024,7 +2333,7 @@ class Compiler
|
|
2024 |
|
2025 |
|
2026 |
/**
|
2027 |
-
* evaluate media query : compile internal value keeping the structure
|
2028 |
*
|
2029 |
* @param array $queryList
|
2030 |
*
|
@@ -2094,7 +2403,7 @@ class Compiler
|
|
2094 |
*
|
2095 |
* @param array $queryList
|
2096 |
*
|
2097 |
-
* @return
|
2098 |
*/
|
2099 |
protected function compileMediaQuery($queryList)
|
2100 |
{
|
@@ -2332,10 +2641,12 @@ class Compiler
|
|
2332 |
if ($rawPath[0] === Type::T_STRING) {
|
2333 |
$path = $this->compileStringContent($rawPath);
|
2334 |
|
2335 |
-
if (strpos($path, 'url(') !== 0 && $
|
2336 |
-
|
2337 |
-
|
2338 |
-
|
|
|
|
|
2339 |
}
|
2340 |
|
2341 |
return true;
|
@@ -2373,7 +2684,7 @@ class Compiler
|
|
2373 |
}
|
2374 |
|
2375 |
/**
|
2376 |
-
* @param $rawPath
|
2377 |
* @return string
|
2378 |
* @throws CompilerException
|
2379 |
*/
|
@@ -2381,12 +2692,12 @@ class Compiler
|
|
2381 |
{
|
2382 |
$path = $this->compileValue($rawPath);
|
2383 |
|
2384 |
-
// case url() without quotes :
|
2385 |
// if this is a real string there can not be CR or LF char
|
2386 |
if (strpos($path, 'url(') === 0) {
|
2387 |
$path = str_replace(array("\r", "\n"), array('', ' '), $path);
|
2388 |
} else {
|
2389 |
-
// if this is a file name in a string, spaces
|
2390 |
$path = $this->reduce($rawPath);
|
2391 |
$path = $this->escapeImportPathString($path);
|
2392 |
$path = $this->compileValue($path);
|
@@ -2424,9 +2735,11 @@ class Compiler
|
|
2424 |
* Append a root directive like @import or @charset as near as the possible from the source code
|
2425 |
* (keeping before comments, @import and @charset coming before in the source code)
|
2426 |
*
|
2427 |
-
* @param string
|
2428 |
-
* @param
|
2429 |
-
* @param array
|
|
|
|
|
2430 |
*/
|
2431 |
protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
|
2432 |
{
|
@@ -2474,7 +2787,9 @@ class Compiler
|
|
2474 |
*
|
2475 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2476 |
* @param string $type
|
2477 |
-
* @param string
|
|
|
|
|
2478 |
*/
|
2479 |
protected function appendOutputLine(OutputBlock $out, $type, $line)
|
2480 |
{
|
@@ -2509,7 +2824,7 @@ class Compiler
|
|
2509 |
* @param array $child
|
2510 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2511 |
*
|
2512 |
-
* @return array
|
2513 |
*/
|
2514 |
protected function compileChild($child, OutputBlock $out)
|
2515 |
{
|
@@ -2523,12 +2838,13 @@ class Compiler
|
|
2523 |
$this->sourceColumn = $child[1]->sourceColumn;
|
2524 |
} elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
|
2525 |
$this->sourceLine = $out->sourceLine;
|
2526 |
-
$
|
2527 |
$this->sourceColumn = $out->sourceColumn;
|
2528 |
|
2529 |
-
if ($
|
2530 |
-
$
|
2531 |
}
|
|
|
2532 |
}
|
2533 |
|
2534 |
switch ($child[0]) {
|
@@ -2653,7 +2969,7 @@ class Compiler
|
|
2653 |
$divider = $this->reduce($divider, true);
|
2654 |
}
|
2655 |
|
2656 |
-
if (\intval($divider->
|
2657 |
$revert = false;
|
2658 |
}
|
2659 |
}
|
@@ -2677,7 +2993,7 @@ class Compiler
|
|
2677 |
$divider = $this->reduce($divider, true);
|
2678 |
}
|
2679 |
|
2680 |
-
if (\intval($divider->
|
2681 |
$revert = false;
|
2682 |
}
|
2683 |
}
|
@@ -2735,12 +3051,21 @@ class Compiler
|
|
2735 |
|
2736 |
case Type::T_EXTEND:
|
2737 |
foreach ($child[1] as $sel) {
|
2738 |
-
$
|
|
|
|
|
|
|
|
|
|
|
2739 |
$results = $this->evalSelectors([$sel]);
|
2740 |
|
2741 |
foreach ($results as $result) {
|
|
|
|
|
|
|
|
|
2742 |
// only use the first one
|
2743 |
-
$result =
|
2744 |
$selectors = $out->selectors;
|
2745 |
|
2746 |
if (! $selectors && isset($child['selfParent'])) {
|
@@ -2790,17 +3115,11 @@ class Compiler
|
|
2790 |
$ret = $this->compileChildren($each->children, $out);
|
2791 |
|
2792 |
if ($ret) {
|
2793 |
-
|
2794 |
-
|
2795 |
-
|
2796 |
-
$this->backPropagateEnv($store, $each->vars);
|
2797 |
-
|
2798 |
-
return $ret;
|
2799 |
-
}
|
2800 |
|
2801 |
-
|
2802 |
-
break;
|
2803 |
-
}
|
2804 |
}
|
2805 |
}
|
2806 |
$store = $this->env->store;
|
@@ -2816,13 +3135,7 @@ class Compiler
|
|
2816 |
$ret = $this->compileChildren($while->children, $out);
|
2817 |
|
2818 |
if ($ret) {
|
2819 |
-
|
2820 |
-
return $ret;
|
2821 |
-
}
|
2822 |
-
|
2823 |
-
if ($ret[1]) {
|
2824 |
-
break;
|
2825 |
-
}
|
2826 |
}
|
2827 |
}
|
2828 |
break;
|
@@ -2830,24 +3143,15 @@ class Compiler
|
|
2830 |
case Type::T_FOR:
|
2831 |
list(, $for) = $child;
|
2832 |
|
2833 |
-
$
|
2834 |
-
$
|
2835 |
-
|
2836 |
-
if (! $start instanceof Node\Number) {
|
2837 |
-
throw $this->error('%s is not a number', $start[0]);
|
2838 |
-
}
|
2839 |
|
2840 |
-
|
2841 |
-
throw $this->error('%s is not a number', $end[0]);
|
2842 |
-
}
|
2843 |
|
2844 |
-
|
2845 |
-
|
2846 |
-
}
|
2847 |
|
2848 |
-
$
|
2849 |
-
$start = $start[1];
|
2850 |
-
$end = $end[1];
|
2851 |
|
2852 |
$d = $start < $end ? 1 : -1;
|
2853 |
|
@@ -2861,23 +3165,17 @@ class Compiler
|
|
2861 |
break;
|
2862 |
}
|
2863 |
|
2864 |
-
$this->set($for->var, new
|
2865 |
$start += $d;
|
2866 |
|
2867 |
$ret = $this->compileChildren($for->children, $out);
|
2868 |
|
2869 |
if ($ret) {
|
2870 |
-
|
2871 |
-
|
2872 |
-
|
2873 |
-
$this->backPropagateEnv($store, [$for->var]);
|
2874 |
-
|
2875 |
-
return $ret;
|
2876 |
-
}
|
2877 |
|
2878 |
-
|
2879 |
-
break;
|
2880 |
-
}
|
2881 |
}
|
2882 |
}
|
2883 |
|
@@ -2887,12 +3185,6 @@ class Compiler
|
|
2887 |
|
2888 |
break;
|
2889 |
|
2890 |
-
case Type::T_BREAK:
|
2891 |
-
return [Type::T_CONTROL, true];
|
2892 |
-
|
2893 |
-
case Type::T_CONTINUE:
|
2894 |
-
return [Type::T_CONTROL, false];
|
2895 |
-
|
2896 |
case Type::T_RETURN:
|
2897 |
return $this->reduce($child[1], true);
|
2898 |
|
@@ -3007,35 +3299,32 @@ class Compiler
|
|
3007 |
case Type::T_DEBUG:
|
3008 |
list(, $value) = $child;
|
3009 |
|
3010 |
-
$fname = $this->sourceNames[$this->sourceIndex];
|
3011 |
$line = $this->sourceLine;
|
3012 |
$value = $this->compileDebugValue($value);
|
3013 |
|
3014 |
-
|
3015 |
break;
|
3016 |
|
3017 |
case Type::T_WARN:
|
3018 |
list(, $value) = $child;
|
3019 |
|
3020 |
-
$fname = $this->sourceNames[$this->sourceIndex];
|
3021 |
$line = $this->sourceLine;
|
3022 |
$value = $this->compileDebugValue($value);
|
3023 |
|
3024 |
-
|
3025 |
break;
|
3026 |
|
3027 |
case Type::T_ERROR:
|
3028 |
list(, $value) = $child;
|
3029 |
|
3030 |
-
$fname = $this->sourceNames[$this->sourceIndex];
|
3031 |
$line = $this->sourceLine;
|
3032 |
$value = $this->compileValue($this->reduce($value, true));
|
3033 |
|
3034 |
throw $this->error("File $fname on line $line ERROR: $value\n");
|
3035 |
|
3036 |
-
case Type::T_CONTROL:
|
3037 |
-
throw $this->error('@break/@continue not permitted in this scope');
|
3038 |
-
|
3039 |
default:
|
3040 |
throw $this->error("unknown child type: $child[0]");
|
3041 |
}
|
@@ -3045,7 +3334,7 @@ class Compiler
|
|
3045 |
* Reduce expression to string
|
3046 |
*
|
3047 |
* @param array $exp
|
3048 |
-
* @param
|
3049 |
*
|
3050 |
* @return array
|
3051 |
*/
|
@@ -3083,11 +3372,11 @@ class Compiler
|
|
3083 |
/**
|
3084 |
* Is truthy?
|
3085 |
*
|
3086 |
-
* @param array $value
|
3087 |
*
|
3088 |
* @return boolean
|
3089 |
*/
|
3090 |
-
|
3091 |
{
|
3092 |
return $value !== static::$false && $value !== static::$null;
|
3093 |
}
|
@@ -3131,15 +3420,15 @@ class Compiler
|
|
3131 |
/**
|
3132 |
* Reduce value
|
3133 |
*
|
3134 |
-
* @param array
|
3135 |
* @param boolean $inExp
|
3136 |
*
|
3137 |
-
* @return
|
3138 |
*/
|
3139 |
protected function reduce($value, $inExp = false)
|
3140 |
{
|
3141 |
-
if (
|
3142 |
-
return
|
3143 |
}
|
3144 |
|
3145 |
switch ($value[0]) {
|
@@ -3157,8 +3446,8 @@ class Compiler
|
|
3157 |
|
3158 |
// special case: looks like css shorthand
|
3159 |
if (
|
3160 |
-
$opName == 'div' && ! $inParens && ! $inExp &&
|
3161 |
-
(($right[0] !== Type::T_NUMBER && $right[2] != '') ||
|
3162 |
($right[0] === Type::T_NUMBER && ! $right->unitless()))
|
3163 |
) {
|
3164 |
return $this->expToString($value);
|
@@ -3188,50 +3477,6 @@ class Compiler
|
|
3188 |
\is_callable([$this, $fn]) &&
|
3189 |
$genOp = true)
|
3190 |
) {
|
3191 |
-
$coerceUnit = false;
|
3192 |
-
|
3193 |
-
if (
|
3194 |
-
! isset($genOp) &&
|
3195 |
-
$left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
|
3196 |
-
) {
|
3197 |
-
$coerceUnit = true;
|
3198 |
-
|
3199 |
-
switch ($opName) {
|
3200 |
-
case 'mul':
|
3201 |
-
$targetUnit = $left[2];
|
3202 |
-
|
3203 |
-
foreach ($right[2] as $unit => $exp) {
|
3204 |
-
$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
|
3205 |
-
}
|
3206 |
-
break;
|
3207 |
-
|
3208 |
-
case 'div':
|
3209 |
-
$targetUnit = $left[2];
|
3210 |
-
|
3211 |
-
foreach ($right[2] as $unit => $exp) {
|
3212 |
-
$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
|
3213 |
-
}
|
3214 |
-
break;
|
3215 |
-
|
3216 |
-
case 'mod':
|
3217 |
-
$targetUnit = $left[2];
|
3218 |
-
break;
|
3219 |
-
|
3220 |
-
default:
|
3221 |
-
$targetUnit = $left->unitless() ? $right[2] : $left[2];
|
3222 |
-
}
|
3223 |
-
|
3224 |
-
$baseUnitLeft = $left->isNormalizable();
|
3225 |
-
$baseUnitRight = $right->isNormalizable();
|
3226 |
-
|
3227 |
-
if ($baseUnitLeft && $baseUnitRight && $baseUnitLeft === $baseUnitRight) {
|
3228 |
-
$left = $left->normalize();
|
3229 |
-
$right = $right->normalize();
|
3230 |
-
} elseif ($coerceUnit) {
|
3231 |
-
$left = new Node\Number($left[1], []);
|
3232 |
-
}
|
3233 |
-
}
|
3234 |
-
|
3235 |
$shouldEval = $inParens || $inExp;
|
3236 |
|
3237 |
if (isset($passOp)) {
|
@@ -3241,10 +3486,6 @@ class Compiler
|
|
3241 |
}
|
3242 |
|
3243 |
if (isset($out)) {
|
3244 |
-
if ($coerceUnit && $out[0] === Type::T_NUMBER) {
|
3245 |
-
$out = $out->coerce($targetUnit);
|
3246 |
-
}
|
3247 |
-
|
3248 |
return $out;
|
3249 |
}
|
3250 |
}
|
@@ -3257,13 +3498,13 @@ class Compiler
|
|
3257 |
$inExp = $inExp || $this->shouldEval($exp);
|
3258 |
$exp = $this->reduce($exp);
|
3259 |
|
3260 |
-
if ($exp
|
3261 |
switch ($op) {
|
3262 |
case '+':
|
3263 |
-
return
|
3264 |
|
3265 |
case '-':
|
3266 |
-
return
|
3267 |
}
|
3268 |
}
|
3269 |
|
@@ -3288,6 +3529,14 @@ class Compiler
|
|
3288 |
foreach ($value[2] as &$item) {
|
3289 |
$item = $this->reduce($item);
|
3290 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3291 |
|
3292 |
return $value;
|
3293 |
|
@@ -3304,7 +3553,7 @@ class Compiler
|
|
3304 |
|
3305 |
case Type::T_STRING:
|
3306 |
foreach ($value[2] as &$item) {
|
3307 |
-
if (\is_array($item) || $item instanceof
|
3308 |
$item = $this->reduce($item);
|
3309 |
}
|
3310 |
}
|
@@ -3315,7 +3564,7 @@ class Compiler
|
|
3315 |
$value[1] = $this->reduce($value[1]);
|
3316 |
|
3317 |
if ($inExp) {
|
3318 |
-
return $value
|
3319 |
}
|
3320 |
|
3321 |
return $value;
|
@@ -3326,7 +3575,7 @@ class Compiler
|
|
3326 |
case Type::T_SELF:
|
3327 |
$selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
|
3328 |
$selfSelector = $this->multiplySelectors($this->env, $selfParent);
|
3329 |
-
$selfSelector = $this->
|
3330 |
|
3331 |
return $selfSelector;
|
3332 |
|
@@ -3338,10 +3587,10 @@ class Compiler
|
|
3338 |
/**
|
3339 |
* Function caller
|
3340 |
*
|
3341 |
-
* @param string $
|
3342 |
-
* @param array
|
3343 |
*
|
3344 |
-
* @return array|
|
3345 |
*/
|
3346 |
protected function fncall($functionReference, $argValues)
|
3347 |
{
|
@@ -3386,7 +3635,7 @@ class Compiler
|
|
3386 |
|
3387 |
// special cases of css valid functions min/max
|
3388 |
$name = strtolower($name);
|
3389 |
-
if (\in_array($name, ['min', 'max'])) {
|
3390 |
$cssFunction = $this->cssValidArg(
|
3391 |
[Type::T_FUNCTION_CALL, $name, $argValues],
|
3392 |
['min', 'max', 'calc', 'env', 'var']
|
@@ -3408,8 +3657,19 @@ class Compiler
|
|
3408 |
}
|
3409 |
}
|
3410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3411 |
protected function cssValidArg($arg, $allowed_function = [], $inFunction = false)
|
3412 |
{
|
|
|
|
|
|
|
|
|
3413 |
switch ($arg[0]) {
|
3414 |
case Type::T_INTERPOLATE:
|
3415 |
return [Type::T_KEYWORD, $this->CompileValue($arg)];
|
@@ -3454,9 +3714,6 @@ class Compiler
|
|
3454 |
}
|
3455 |
return $this->stringifyFncallArgs($arg);
|
3456 |
|
3457 |
-
case Type::T_NUMBER:
|
3458 |
-
return $this->stringifyFncallArgs($arg);
|
3459 |
-
|
3460 |
case Type::T_LIST:
|
3461 |
if (!$inFunction) {
|
3462 |
return false;
|
@@ -3494,11 +3751,16 @@ class Compiler
|
|
3494 |
|
3495 |
/**
|
3496 |
* Reformat fncall arguments to proper css function output
|
3497 |
-
*
|
3498 |
-
* @
|
|
|
|
|
3499 |
*/
|
3500 |
protected function stringifyFncallArgs($arg)
|
3501 |
{
|
|
|
|
|
|
|
3502 |
|
3503 |
switch ($arg[0]) {
|
3504 |
case Type::T_LIST:
|
@@ -3563,6 +3825,21 @@ class Compiler
|
|
3563 |
$libName = $f[1];
|
3564 |
$prototype = isset(static::$$libName) ? static::$$libName : null;
|
3565 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3566 |
return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
|
3567 |
}
|
3568 |
|
@@ -3585,14 +3862,20 @@ class Compiler
|
|
3585 |
/**
|
3586 |
* Normalize value
|
3587 |
*
|
3588 |
-
* @
|
3589 |
*
|
3590 |
-
* @
|
|
|
|
|
3591 |
*/
|
3592 |
public function normalizeValue($value)
|
3593 |
{
|
3594 |
$value = $this->coerceForExpression($this->reduce($value));
|
3595 |
|
|
|
|
|
|
|
|
|
3596 |
switch ($value[0]) {
|
3597 |
case Type::T_LIST:
|
3598 |
$value = $this->extractInterpolation($value);
|
@@ -3614,9 +3897,6 @@ class Compiler
|
|
3614 |
case Type::T_STRING:
|
3615 |
return [$value[0], '"', [$this->compileStringContent($value)]];
|
3616 |
|
3617 |
-
case Type::T_NUMBER:
|
3618 |
-
return $value->normalize();
|
3619 |
-
|
3620 |
case Type::T_INTERPOLATE:
|
3621 |
return [Type::T_KEYWORD, $this->compileValue($value)];
|
3622 |
|
@@ -3628,74 +3908,66 @@ class Compiler
|
|
3628 |
/**
|
3629 |
* Add numbers
|
3630 |
*
|
3631 |
-
* @param
|
3632 |
-
* @param
|
3633 |
*
|
3634 |
-
* @return
|
3635 |
*/
|
3636 |
-
protected function opAddNumberNumber($left, $right)
|
3637 |
{
|
3638 |
-
return
|
3639 |
}
|
3640 |
|
3641 |
/**
|
3642 |
* Multiply numbers
|
3643 |
*
|
3644 |
-
* @param
|
3645 |
-
* @param
|
3646 |
*
|
3647 |
-
* @return
|
3648 |
*/
|
3649 |
-
protected function opMulNumberNumber($left, $right)
|
3650 |
{
|
3651 |
-
return
|
3652 |
}
|
3653 |
|
3654 |
/**
|
3655 |
* Subtract numbers
|
3656 |
*
|
3657 |
-
* @param
|
3658 |
-
* @param
|
3659 |
*
|
3660 |
-
* @return
|
3661 |
*/
|
3662 |
-
protected function opSubNumberNumber($left, $right)
|
3663 |
{
|
3664 |
-
return
|
3665 |
}
|
3666 |
|
3667 |
/**
|
3668 |
* Divide numbers
|
3669 |
*
|
3670 |
-
* @param
|
3671 |
-
* @param
|
3672 |
*
|
3673 |
-
* @return
|
3674 |
*/
|
3675 |
-
protected function opDivNumberNumber($left, $right)
|
3676 |
{
|
3677 |
-
|
3678 |
-
return ($left[1] == 0) ? static::$NaN : static::$Infinity;
|
3679 |
-
}
|
3680 |
-
|
3681 |
-
return new Node\Number($left[1] / $right[1], $left[2]);
|
3682 |
}
|
3683 |
|
3684 |
/**
|
3685 |
* Mod numbers
|
3686 |
*
|
3687 |
-
* @param
|
3688 |
-
* @param
|
3689 |
*
|
3690 |
-
* @return
|
3691 |
*/
|
3692 |
-
protected function opModNumberNumber($left, $right)
|
3693 |
{
|
3694 |
-
|
3695 |
-
return static::$NaN;
|
3696 |
-
}
|
3697 |
-
|
3698 |
-
return new Node\Number($left[1] % $right[1], $left[2]);
|
3699 |
}
|
3700 |
|
3701 |
/**
|
@@ -3734,11 +4006,11 @@ class Compiler
|
|
3734 |
/**
|
3735 |
* Boolean and
|
3736 |
*
|
3737 |
-
* @param array
|
3738 |
-
* @param array
|
3739 |
* @param boolean $shouldEval
|
3740 |
*
|
3741 |
-
* @return array|null
|
3742 |
*/
|
3743 |
protected function opAnd($left, $right, $shouldEval)
|
3744 |
{
|
@@ -3762,11 +4034,11 @@ class Compiler
|
|
3762 |
/**
|
3763 |
* Boolean or
|
3764 |
*
|
3765 |
-
* @param array
|
3766 |
-
* @param array
|
3767 |
* @param boolean $shouldEval
|
3768 |
*
|
3769 |
-
* @return array|null
|
3770 |
*/
|
3771 |
protected function opOr($left, $right, $shouldEval)
|
3772 |
{
|
@@ -3798,6 +4070,15 @@ class Compiler
|
|
3798 |
*/
|
3799 |
protected function opColorColor($op, $left, $right)
|
3800 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3801 |
$out = [Type::T_COLOR];
|
3802 |
|
3803 |
foreach ([1, 2, 3] as $i) {
|
@@ -3858,13 +4139,21 @@ class Compiler
|
|
3858 |
*
|
3859 |
* @param string $op
|
3860 |
* @param array $left
|
3861 |
-
* @param
|
3862 |
*
|
3863 |
* @return array
|
3864 |
*/
|
3865 |
-
protected function opColorNumber($op, $left, $right)
|
3866 |
{
|
3867 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3868 |
|
3869 |
return $this->opColorColor(
|
3870 |
$op,
|
@@ -3877,14 +4166,22 @@ class Compiler
|
|
3877 |
* Compare number and color
|
3878 |
*
|
3879 |
* @param string $op
|
3880 |
-
* @param
|
3881 |
* @param array $right
|
3882 |
*
|
3883 |
* @return array
|
3884 |
*/
|
3885 |
-
protected function opNumberColor($op, $left, $right)
|
3886 |
{
|
3887 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3888 |
|
3889 |
return $this->opColorColor(
|
3890 |
$op,
|
@@ -3896,8 +4193,8 @@ class Compiler
|
|
3896 |
/**
|
3897 |
* Compare number1 == number2
|
3898 |
*
|
3899 |
-
* @param array $left
|
3900 |
-
* @param array $right
|
3901 |
*
|
3902 |
* @return array
|
3903 |
*/
|
@@ -3917,8 +4214,8 @@ class Compiler
|
|
3917 |
/**
|
3918 |
* Compare number1 != number2
|
3919 |
*
|
3920 |
-
* @param array $left
|
3921 |
-
* @param array $right
|
3922 |
*
|
3923 |
* @return array
|
3924 |
*/
|
@@ -3936,70 +4233,81 @@ class Compiler
|
|
3936 |
}
|
3937 |
|
3938 |
/**
|
3939 |
-
* Compare number1
|
3940 |
*
|
3941 |
-
* @param
|
3942 |
-
* @param
|
3943 |
*
|
3944 |
* @return array
|
3945 |
*/
|
3946 |
-
protected function
|
3947 |
{
|
3948 |
-
return $this->toBool($left
|
3949 |
}
|
3950 |
|
3951 |
/**
|
3952 |
-
* Compare number1
|
3953 |
*
|
3954 |
-
* @param
|
3955 |
-
* @param
|
3956 |
*
|
3957 |
* @return array
|
3958 |
*/
|
3959 |
-
protected function
|
3960 |
{
|
3961 |
-
return $this->toBool(
|
3962 |
}
|
3963 |
|
3964 |
/**
|
3965 |
-
* Compare number1
|
3966 |
*
|
3967 |
-
* @param
|
3968 |
-
* @param
|
3969 |
*
|
3970 |
* @return array
|
3971 |
*/
|
3972 |
-
protected function
|
3973 |
{
|
3974 |
-
return $this->toBool($left
|
3975 |
}
|
3976 |
|
3977 |
/**
|
3978 |
-
* Compare number1
|
3979 |
*
|
3980 |
-
* @param
|
3981 |
-
* @param
|
3982 |
*
|
3983 |
* @return array
|
3984 |
*/
|
3985 |
-
protected function
|
3986 |
{
|
3987 |
-
return $this->toBool($left
|
3988 |
}
|
3989 |
|
3990 |
/**
|
3991 |
-
*
|
3992 |
*
|
3993 |
-
* @param
|
3994 |
-
* @param
|
3995 |
*
|
3996 |
-
* @return
|
3997 |
*/
|
3998 |
-
protected function
|
3999 |
{
|
4000 |
-
|
|
|
4001 |
|
4002 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4003 |
}
|
4004 |
|
4005 |
/**
|
@@ -4007,7 +4315,7 @@ class Compiler
|
|
4007 |
*
|
4008 |
* @api
|
4009 |
*
|
4010 |
-
* @param
|
4011 |
*
|
4012 |
* @return array
|
4013 |
*/
|
@@ -4016,6 +4324,53 @@ class Compiler
|
|
4016 |
return $thing ? static::$true : static::$false;
|
4017 |
}
|
4018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4019 |
/**
|
4020 |
* Compiles a primitive value into a CSS property value.
|
4021 |
*
|
@@ -4029,17 +4384,22 @@ class Compiler
|
|
4029 |
*
|
4030 |
* @api
|
4031 |
*
|
4032 |
-
* @param array $value
|
|
|
4033 |
*
|
4034 |
-
* @return string
|
4035 |
*/
|
4036 |
-
public function compileValue($value)
|
4037 |
{
|
4038 |
$value = $this->reduce($value);
|
4039 |
|
|
|
|
|
|
|
|
|
4040 |
switch ($value[0]) {
|
4041 |
case Type::T_KEYWORD:
|
4042 |
-
return $value[1];
|
4043 |
|
4044 |
case Type::T_COLOR:
|
4045 |
// [1] - red component (either number for a %)
|
@@ -4063,7 +4423,7 @@ class Compiler
|
|
4063 |
}
|
4064 |
|
4065 |
if (is_numeric($alpha)) {
|
4066 |
-
$a = new
|
4067 |
} else {
|
4068 |
$a = $alpha;
|
4069 |
}
|
@@ -4091,32 +4451,35 @@ class Compiler
|
|
4091 |
|
4092 |
return $h;
|
4093 |
|
4094 |
-
case Type::T_NUMBER:
|
4095 |
-
return $value->output($this);
|
4096 |
-
|
4097 |
case Type::T_STRING:
|
4098 |
-
$content = $this->compileStringContent($value);
|
|
|
|
|
|
|
|
|
|
|
4099 |
|
4100 |
-
if ($value[1]) {
|
4101 |
// force double quote as string quote for the output in certain cases
|
4102 |
if (
|
4103 |
$value[1] === "'" &&
|
4104 |
-
strpos($content, '"') === false &&
|
4105 |
-
strpbrk($content, '{}') !== false
|
4106 |
) {
|
4107 |
$value[1] = '"';
|
|
|
|
|
|
|
|
|
|
|
4108 |
}
|
4109 |
-
|
4110 |
-
|
4111 |
-
array("\r" , ' ' , '\\f', '\\\\', '\\a', '\\' . $value[1]),
|
4112 |
-
$content
|
4113 |
-
);
|
4114 |
}
|
4115 |
|
4116 |
return $value[1] . $content . $value[1];
|
4117 |
|
4118 |
case Type::T_FUNCTION:
|
4119 |
-
$args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
|
4120 |
|
4121 |
return "$value[1]($args)";
|
4122 |
|
@@ -4129,7 +4492,7 @@ class Compiler
|
|
4129 |
$value = $this->extractInterpolation($value);
|
4130 |
|
4131 |
if ($value[0] !== Type::T_LIST) {
|
4132 |
-
return $this->compileValue($value);
|
4133 |
}
|
4134 |
|
4135 |
list(, $delim, $items) = $value;
|
@@ -4161,12 +4524,28 @@ class Compiler
|
|
4161 |
|
4162 |
$filtered = [];
|
4163 |
|
|
|
4164 |
foreach ($items as $item) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4165 |
if ($item[0] === Type::T_NULL) {
|
4166 |
continue;
|
4167 |
}
|
|
|
|
|
|
|
4168 |
|
4169 |
-
$compiled = $this->compileValue($item);
|
4170 |
|
4171 |
if ($prefix_value && \strlen($compiled)) {
|
4172 |
$compiled = $prefix_value . $compiled;
|
@@ -4183,7 +4562,7 @@ class Compiler
|
|
4183 |
$filtered = [];
|
4184 |
|
4185 |
for ($i = 0, $s = \count($keys); $i < $s; $i++) {
|
4186 |
-
$filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
|
4187 |
}
|
4188 |
|
4189 |
array_walk($filtered, function (&$value, $key) {
|
@@ -4204,7 +4583,7 @@ class Compiler
|
|
4204 |
}
|
4205 |
|
4206 |
$left = \count($left[2]) > 0
|
4207 |
-
? $this->compileValue($left) . $delim . $whiteLeft
|
4208 |
: '';
|
4209 |
|
4210 |
$delim = $right[1];
|
@@ -4214,14 +4593,18 @@ class Compiler
|
|
4214 |
}
|
4215 |
|
4216 |
$right = \count($right[2]) > 0 ?
|
4217 |
-
$whiteRight . $delim . $this->compileValue($right) : '';
|
4218 |
|
4219 |
-
return $left . $this->compileValue($interpolate) . $right;
|
4220 |
|
4221 |
case Type::T_INTERPOLATE:
|
4222 |
// strip quotes if it's a string
|
4223 |
$reduced = $this->reduce($value[1]);
|
4224 |
|
|
|
|
|
|
|
|
|
4225 |
switch ($reduced[0]) {
|
4226 |
case Type::T_LIST:
|
4227 |
$reduced = $this->extractInterpolation($reduced);
|
@@ -4243,14 +4626,12 @@ class Compiler
|
|
4243 |
continue;
|
4244 |
}
|
4245 |
|
4246 |
-
$
|
4247 |
-
|
4248 |
-
|
4249 |
-
$filtered[] = $
|
4250 |
-
} elseif ($temp[0] === Type::T_KEYWORD) {
|
4251 |
-
$filtered[] = $temp[1];
|
4252 |
} else {
|
4253 |
-
$filtered[] = $this->compileValue($
|
4254 |
}
|
4255 |
}
|
4256 |
|
@@ -4258,14 +4639,14 @@ class Compiler
|
|
4258 |
break;
|
4259 |
|
4260 |
case Type::T_STRING:
|
4261 |
-
$reduced = [Type::
|
4262 |
break;
|
4263 |
|
4264 |
case Type::T_NULL:
|
4265 |
$reduced = [Type::T_KEYWORD, ''];
|
4266 |
}
|
4267 |
|
4268 |
-
return $this->compileValue($reduced);
|
4269 |
|
4270 |
case Type::T_NULL:
|
4271 |
return 'null';
|
@@ -4279,14 +4660,18 @@ class Compiler
|
|
4279 |
}
|
4280 |
|
4281 |
/**
|
4282 |
-
* @param array $value
|
4283 |
*
|
4284 |
-
* @return
|
4285 |
*/
|
4286 |
protected function compileDebugValue($value)
|
4287 |
{
|
4288 |
$value = $this->reduce($value, true);
|
4289 |
|
|
|
|
|
|
|
|
|
4290 |
switch ($value[0]) {
|
4291 |
case Type::T_STRING:
|
4292 |
return $this->compileStringContent($value);
|
@@ -4302,28 +4687,52 @@ class Compiler
|
|
4302 |
* @param array $list
|
4303 |
*
|
4304 |
* @return string
|
|
|
|
|
4305 |
*/
|
4306 |
protected function flattenList($list)
|
4307 |
{
|
|
|
|
|
4308 |
return $this->compileValue($list);
|
4309 |
}
|
4310 |
|
4311 |
/**
|
4312 |
-
*
|
4313 |
*
|
4314 |
-
* @
|
|
|
|
|
|
|
4315 |
*
|
4316 |
* @return string
|
4317 |
*/
|
4318 |
-
|
4319 |
{
|
4320 |
-
$
|
|
|
|
|
4321 |
|
4322 |
-
|
4323 |
-
|
4324 |
-
|
4325 |
-
|
4326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4327 |
}
|
4328 |
}
|
4329 |
|
@@ -4533,9 +4942,11 @@ class Compiler
|
|
4533 |
/**
|
4534 |
* Convert env linked list to stack
|
4535 |
*
|
4536 |
-
* @param
|
4537 |
*
|
4538 |
-
* @return
|
|
|
|
|
4539 |
*/
|
4540 |
protected function compactEnv(Environment $env)
|
4541 |
{
|
@@ -4549,9 +4960,11 @@ class Compiler
|
|
4549 |
/**
|
4550 |
* Convert env stack to singly linked list
|
4551 |
*
|
4552 |
-
* @param
|
4553 |
*
|
4554 |
-
* @return
|
|
|
|
|
4555 |
*/
|
4556 |
protected function extractEnv($envs)
|
4557 |
{
|
@@ -4587,6 +5000,8 @@ class Compiler
|
|
4587 |
|
4588 |
/**
|
4589 |
* Pop environment
|
|
|
|
|
4590 |
*/
|
4591 |
protected function popEnv()
|
4592 |
{
|
@@ -4597,8 +5012,10 @@ class Compiler
|
|
4597 |
/**
|
4598 |
* Propagate vars from a just poped Env (used in @each and @for)
|
4599 |
*
|
4600 |
-
* @param array
|
4601 |
-
* @param null|
|
|
|
|
|
4602 |
*/
|
4603 |
protected function backPropagateEnv($store, $excludedVars = null)
|
4604 |
{
|
@@ -4627,6 +5044,8 @@ class Compiler
|
|
4627 |
* @param boolean $shadow
|
4628 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
4629 |
* @param mixed $valueUnreduced
|
|
|
|
|
4630 |
*/
|
4631 |
protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
|
4632 |
{
|
@@ -4650,6 +5069,8 @@ class Compiler
|
|
4650 |
* @param mixed $value
|
4651 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
4652 |
* @param mixed $valueUnreduced
|
|
|
|
|
4653 |
*/
|
4654 |
protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
|
4655 |
{
|
@@ -4708,6 +5129,8 @@ class Compiler
|
|
4708 |
* @param mixed $value
|
4709 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
4710 |
* @param mixed $valueUnreduced
|
|
|
|
|
4711 |
*/
|
4712 |
protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
|
4713 |
{
|
@@ -4721,7 +5144,7 @@ class Compiler
|
|
4721 |
/**
|
4722 |
* Get variable
|
4723 |
*
|
4724 |
-
* @
|
4725 |
*
|
4726 |
* @param string $name
|
4727 |
* @param boolean $shouldThrow
|
@@ -4804,6 +5227,8 @@ class Compiler
|
|
4804 |
* Inject variables
|
4805 |
*
|
4806 |
* @param array $args
|
|
|
|
|
4807 |
*/
|
4808 |
protected function injectVariables(array $args)
|
4809 |
{
|
@@ -4818,7 +5243,7 @@ class Compiler
|
|
4818 |
$name = substr($name, 1);
|
4819 |
}
|
4820 |
|
4821 |
-
if (! $parser->parseValue($strValue, $value)) {
|
4822 |
$value = $this->coerceValue($strValue);
|
4823 |
}
|
4824 |
|
@@ -4826,16 +5251,59 @@ class Compiler
|
|
4826 |
}
|
4827 |
}
|
4828 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4829 |
/**
|
4830 |
* Set variables
|
4831 |
*
|
4832 |
* @api
|
4833 |
*
|
4834 |
* @param array $variables
|
|
|
|
|
|
|
|
|
4835 |
*/
|
4836 |
public function setVariables(array $variables)
|
4837 |
{
|
4838 |
-
|
|
|
|
|
4839 |
}
|
4840 |
|
4841 |
/**
|
@@ -4844,6 +5312,8 @@ class Compiler
|
|
4844 |
* @api
|
4845 |
*
|
4846 |
* @param string $name
|
|
|
|
|
4847 |
*/
|
4848 |
public function unsetVariable($name)
|
4849 |
{
|
@@ -4865,13 +5335,15 @@ class Compiler
|
|
4865 |
/**
|
4866 |
* Adds to list of parsed files
|
4867 |
*
|
4868 |
-
* @
|
4869 |
*
|
4870 |
-
* @param string $path
|
|
|
|
|
4871 |
*/
|
4872 |
public function addParsedFile($path)
|
4873 |
{
|
4874 |
-
if (
|
4875 |
$this->parsedFiles[realpath($path)] = filemtime($path);
|
4876 |
}
|
4877 |
}
|
@@ -4879,12 +5351,12 @@ class Compiler
|
|
4879 |
/**
|
4880 |
* Returns list of parsed files
|
4881 |
*
|
4882 |
-
* @
|
4883 |
-
*
|
4884 |
-
* @return array
|
4885 |
*/
|
4886 |
public function getParsedFiles()
|
4887 |
{
|
|
|
4888 |
return $this->parsedFiles;
|
4889 |
}
|
4890 |
|
@@ -4894,6 +5366,8 @@ class Compiler
|
|
4894 |
* @api
|
4895 |
*
|
4896 |
* @param string|callable $path
|
|
|
|
|
4897 |
*/
|
4898 |
public function addImportPath($path)
|
4899 |
{
|
@@ -4907,11 +5381,24 @@ class Compiler
|
|
4907 |
*
|
4908 |
* @api
|
4909 |
*
|
4910 |
-
* @param string|array $path
|
|
|
|
|
4911 |
*/
|
4912 |
public function setImportPaths($path)
|
4913 |
{
|
4914 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4915 |
}
|
4916 |
|
4917 |
/**
|
@@ -4921,6 +5408,8 @@ class Compiler
|
|
4921 |
*
|
4922 |
* @param integer $numberPrecision
|
4923 |
*
|
|
|
|
|
4924 |
* @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
|
4925 |
*/
|
4926 |
public function setNumberPrecision($numberPrecision)
|
@@ -4929,15 +5418,51 @@ class Compiler
|
|
4929 |
. 'The default is enough for all browsers.', E_USER_DEPRECATED);
|
4930 |
}
|
4931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4932 |
/**
|
4933 |
* Set formatter
|
4934 |
*
|
4935 |
* @api
|
4936 |
*
|
4937 |
* @param string $formatterName
|
|
|
|
|
|
|
|
|
4938 |
*/
|
4939 |
public function setFormatter($formatterName)
|
4940 |
{
|
|
|
|
|
|
|
|
|
|
|
4941 |
$this->formatter = $formatterName;
|
4942 |
}
|
4943 |
|
@@ -4947,10 +5472,15 @@ class Compiler
|
|
4947 |
* @api
|
4948 |
*
|
4949 |
* @param string $lineNumberStyle
|
|
|
|
|
|
|
|
|
4950 |
*/
|
4951 |
public function setLineNumberStyle($lineNumberStyle)
|
4952 |
{
|
4953 |
-
|
|
|
4954 |
}
|
4955 |
|
4956 |
/**
|
@@ -4959,6 +5489,10 @@ class Compiler
|
|
4959 |
* @api
|
4960 |
*
|
4961 |
* @param integer $sourceMap
|
|
|
|
|
|
|
|
|
4962 |
*/
|
4963 |
public function setSourceMap($sourceMap)
|
4964 |
{
|
@@ -4971,6 +5505,10 @@ class Compiler
|
|
4971 |
* @api
|
4972 |
*
|
4973 |
* @param array $sourceMapOptions
|
|
|
|
|
|
|
|
|
4974 |
*/
|
4975 |
public function setSourceMapOptions($sourceMapOptions)
|
4976 |
{
|
@@ -4982,13 +5520,23 @@ class Compiler
|
|
4982 |
*
|
4983 |
* @api
|
4984 |
*
|
4985 |
-
* @param string
|
4986 |
-
* @param callable
|
4987 |
-
* @param
|
|
|
|
|
4988 |
*/
|
4989 |
-
public function registerFunction($name, $
|
4990 |
{
|
4991 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4992 |
}
|
4993 |
|
4994 |
/**
|
@@ -4997,6 +5545,8 @@ class Compiler
|
|
4997 |
* @api
|
4998 |
*
|
4999 |
* @param string $name
|
|
|
|
|
5000 |
*/
|
5001 |
public function unregisterFunction($name)
|
5002 |
{
|
@@ -5009,9 +5559,15 @@ class Compiler
|
|
5009 |
* @api
|
5010 |
*
|
5011 |
* @param string $name
|
|
|
|
|
|
|
|
|
5012 |
*/
|
5013 |
public function addFeature($name)
|
5014 |
{
|
|
|
|
|
5015 |
$this->registeredFeatures[$name] = true;
|
5016 |
}
|
5017 |
|
@@ -5020,13 +5576,24 @@ class Compiler
|
|
5020 |
*
|
5021 |
* @param string $path
|
5022 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
|
|
|
|
5023 |
*/
|
5024 |
protected function importFile($path, OutputBlock $out)
|
5025 |
{
|
5026 |
-
$this->pushCallStack('import ' . $path);
|
5027 |
// see if tree is cached
|
5028 |
$realPath = realpath($path);
|
5029 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5030 |
if (isset($this->importCache[$realPath])) {
|
5031 |
$this->handleImportLoop($realPath);
|
5032 |
|
@@ -5039,78 +5606,109 @@ class Compiler
|
|
5039 |
$this->importCache[$realPath] = $tree;
|
5040 |
}
|
5041 |
|
5042 |
-
$
|
|
|
5043 |
|
5044 |
-
array_unshift($this->importPaths, $pi['dirname']);
|
5045 |
$this->compileChildrenNoReturn($tree->children, $out);
|
5046 |
-
|
5047 |
$this->popCallStack();
|
5048 |
}
|
5049 |
|
5050 |
/**
|
5051 |
-
*
|
5052 |
*
|
5053 |
-
* @
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5054 |
*
|
5055 |
* @param string $url
|
5056 |
*
|
5057 |
-
* @return
|
5058 |
*/
|
5059 |
-
public function
|
5060 |
{
|
5061 |
-
|
|
|
5062 |
|
5063 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5064 |
|
5065 |
-
|
5066 |
-
|
5067 |
-
|
5068 |
|
5069 |
-
|
5070 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5071 |
|
5072 |
-
|
5073 |
-
|
|
|
5074 |
}
|
|
|
|
|
5075 |
|
5076 |
-
|
5077 |
-
|
5078 |
-
|
5079 |
-
|
|
|
5080 |
}
|
5081 |
}
|
5082 |
|
5083 |
foreach ($this->importPaths as $dir) {
|
5084 |
if (\is_string($dir)) {
|
5085 |
-
|
5086 |
-
|
5087 |
-
|
5088 |
-
$
|
5089 |
-
! empty($dir) &&
|
5090 |
-
substr($dir, -1) !== '/' &&
|
5091 |
-
substr($full, 0, 1) !== '/'
|
5092 |
-
) ? '/' : '';
|
5093 |
-
$full = $dir . $separator . $full;
|
5094 |
-
|
5095 |
-
if (is_file($file = $full)) {
|
5096 |
-
$found[] = $file;
|
5097 |
-
}
|
5098 |
-
if (! $isPartial) {
|
5099 |
-
$full = dirname($full) . '/_' . basename($full);
|
5100 |
-
if (is_file($file = $full)) {
|
5101 |
-
$found[] = $file;
|
5102 |
-
}
|
5103 |
-
}
|
5104 |
-
if ($found) {
|
5105 |
-
if (\count($found) === 1) {
|
5106 |
-
return reset($found);
|
5107 |
-
}
|
5108 |
-
if (\count($found) > 1) {
|
5109 |
-
throw $this->error(
|
5110 |
-
"Error: It's not clear which file to import. Found: " . implode(', ', $found)
|
5111 |
-
);
|
5112 |
-
}
|
5113 |
-
}
|
5114 |
}
|
5115 |
} elseif (\is_callable($dir)) {
|
5116 |
// check custom callback for import path
|
@@ -5122,13 +5720,147 @@ class Compiler
|
|
5122 |
}
|
5123 |
}
|
5124 |
|
5125 |
-
if ($
|
5126 |
-
|
5127 |
-
|
|
|
|
|
|
|
|
|
5128 |
}
|
5129 |
}
|
5130 |
|
5131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5132 |
}
|
5133 |
|
5134 |
/**
|
@@ -5136,10 +5868,20 @@ class Compiler
|
|
5136 |
*
|
5137 |
* @api
|
5138 |
*
|
5139 |
-
* @param string $encoding
|
|
|
|
|
|
|
|
|
5140 |
*/
|
5141 |
public function setEncoding($encoding)
|
5142 |
{
|
|
|
|
|
|
|
|
|
|
|
|
|
5143 |
$this->encoding = $encoding;
|
5144 |
}
|
5145 |
|
@@ -5167,9 +5909,13 @@ class Compiler
|
|
5167 |
* @api
|
5168 |
*
|
5169 |
* @return array
|
|
|
|
|
5170 |
*/
|
5171 |
public function getSourcePosition()
|
5172 |
{
|
|
|
|
|
5173 |
$sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
|
5174 |
|
5175 |
return [$sourceFile, $this->sourceLine, $this->sourceColumn];
|
@@ -5199,7 +5945,7 @@ class Compiler
|
|
5199 |
/**
|
5200 |
* Build an error (exception)
|
5201 |
*
|
5202 |
-
* @
|
5203 |
*
|
5204 |
* @param string $msg Message with optional sprintf()-style vararg parameters
|
5205 |
*
|
@@ -5212,23 +5958,35 @@ class Compiler
|
|
5212 |
}
|
5213 |
|
5214 |
if (! $this->ignoreCallStackMessage) {
|
5215 |
-
$
|
5216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5217 |
|
5218 |
-
|
5219 |
-
|
5220 |
-
|
5221 |
|
5222 |
-
|
5223 |
|
5224 |
-
|
5225 |
|
5226 |
-
|
5227 |
-
|
5228 |
-
}
|
5229 |
}
|
5230 |
|
5231 |
-
return
|
5232 |
}
|
5233 |
|
5234 |
/**
|
@@ -5236,9 +5994,13 @@ class Compiler
|
|
5236 |
* @param array $ExpectedArgs
|
5237 |
* @param int $nbActual
|
5238 |
* @return CompilerException
|
|
|
|
|
5239 |
*/
|
5240 |
public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
|
5241 |
{
|
|
|
|
|
5242 |
$nbExpected = \count($ExpectedArgs);
|
5243 |
|
5244 |
if ($nbActual > $nbExpected) {
|
@@ -5267,8 +6029,8 @@ class Compiler
|
|
5267 |
/**
|
5268 |
* Beautify call stack for output
|
5269 |
*
|
5270 |
-
* @param boolean
|
5271 |
-
* @param null
|
5272 |
*
|
5273 |
* @return string
|
5274 |
*/
|
@@ -5282,7 +6044,7 @@ class Compiler
|
|
5282 |
if ($all || (isset($call['n']) && $call['n'])) {
|
5283 |
$msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
|
5284 |
$msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
|
5285 |
-
? $this->sourceNames[$call[Parser::SOURCE_INDEX]]
|
5286 |
: '(unknown file)');
|
5287 |
$msg .= ' on line ' . $call[Parser::SOURCE_LINE];
|
5288 |
|
@@ -5314,6 +6076,10 @@ class Compiler
|
|
5314 |
|
5315 |
$file = $this->sourceNames[$env->block->sourceIndex];
|
5316 |
|
|
|
|
|
|
|
|
|
5317 |
if (realpath($file) === $name) {
|
5318 |
throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
|
5319 |
}
|
@@ -5326,7 +6092,7 @@ class Compiler
|
|
5326 |
* @param Object $func
|
5327 |
* @param array $argValues
|
5328 |
*
|
5329 |
-
* @return array
|
5330 |
*/
|
5331 |
protected function callScssFunction($func, $argValues)
|
5332 |
{
|
@@ -5366,11 +6132,11 @@ class Compiler
|
|
5366 |
* Call built-in and registered (PHP) functions
|
5367 |
*
|
5368 |
* @param string $name
|
5369 |
-
* @param
|
5370 |
* @param array $prototype
|
5371 |
* @param array $args
|
5372 |
*
|
5373 |
-
* @return array
|
5374 |
*/
|
5375 |
protected function callNativeFunction($name, $function, $prototype, $args)
|
5376 |
{
|
@@ -5382,15 +6148,11 @@ class Compiler
|
|
5382 |
}
|
5383 |
@list($sorted, $kwargs) = $sorted_kwargs;
|
5384 |
|
5385 |
-
if ($name !== 'if'
|
5386 |
-
$inExp = true;
|
5387 |
-
|
5388 |
-
if ($name === 'join') {
|
5389 |
-
$inExp = false;
|
5390 |
-
}
|
5391 |
-
|
5392 |
foreach ($sorted as &$val) {
|
5393 |
-
|
|
|
|
|
5394 |
}
|
5395 |
}
|
5396 |
|
@@ -5400,7 +6162,13 @@ class Compiler
|
|
5400 |
return null;
|
5401 |
}
|
5402 |
|
5403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
5404 |
}
|
5405 |
|
5406 |
/**
|
@@ -5418,7 +6186,11 @@ class Compiler
|
|
5418 |
|
5419 |
/**
|
5420 |
* Normalize native function name
|
5421 |
-
*
|
|
|
|
|
|
|
|
|
5422 |
* @return string
|
5423 |
*/
|
5424 |
public static function normalizeNativeFunctionName($name)
|
@@ -5436,7 +6208,11 @@ class Compiler
|
|
5436 |
|
5437 |
/**
|
5438 |
* Check if a function is a native built-in scss function, for css parsing
|
5439 |
-
*
|
|
|
|
|
|
|
|
|
5440 |
* @return bool
|
5441 |
*/
|
5442 |
public static function isNativeFunction($name)
|
@@ -5448,7 +6224,7 @@ class Compiler
|
|
5448 |
* Sorts keyword arguments
|
5449 |
*
|
5450 |
* @param string $functionName
|
5451 |
-
* @param array $prototypes
|
5452 |
* @param array $args
|
5453 |
*
|
5454 |
* @return array|null
|
@@ -5484,229 +6260,352 @@ class Compiler
|
|
5484 |
// notation 100 127 255 / 0 is in fact a simple list of 4 values
|
5485 |
foreach ($args as $k => $arg) {
|
5486 |
if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
|
5487 |
-
$
|
5488 |
-
|
5489 |
-
if ($last[0] === Type::T_EXPRESSION && $last[1] === '/') {
|
5490 |
-
array_pop($arg[1][2]);
|
5491 |
-
$arg[1][2][] = $last[2];
|
5492 |
-
$arg[1][2][] = $last[3];
|
5493 |
-
$args[$k] = $arg;
|
5494 |
-
}
|
5495 |
}
|
5496 |
}
|
5497 |
}
|
5498 |
|
5499 |
-
$
|
5500 |
|
5501 |
if (! \is_array(reset($prototypes))) {
|
5502 |
$prototypes = [$prototypes];
|
5503 |
}
|
5504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5505 |
$keyArgs = [];
|
5506 |
|
5507 |
-
|
5508 |
-
|
5509 |
-
$exceptionMessage = '';
|
5510 |
|
5511 |
-
|
5512 |
-
|
|
|
|
|
|
|
5513 |
|
5514 |
-
|
5515 |
-
|
5516 |
-
$
|
5517 |
-
|
5518 |
|
5519 |
-
|
5520 |
-
|
|
|
5521 |
|
5522 |
-
|
5523 |
-
|
5524 |
-
$default = [Type::T_KEYWORD, 'null'];
|
5525 |
-
} else {
|
5526 |
-
if (\is_null($parser)) {
|
5527 |
-
$parser = $this->parserFactory(__METHOD__);
|
5528 |
-
}
|
5529 |
|
5530 |
-
|
5531 |
-
|
5532 |
-
|
5533 |
|
5534 |
-
|
|
|
5535 |
|
5536 |
-
|
5537 |
-
|
5538 |
-
|
5539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5540 |
|
5541 |
-
|
|
|
|
|
5542 |
}
|
5543 |
|
5544 |
-
$
|
5545 |
-
$
|
|
|
5546 |
|
5547 |
-
|
5548 |
-
|
5549 |
-
$lastDef = end($argDef);
|
5550 |
|
5551 |
-
|
5552 |
-
|
5553 |
-
|
|
|
|
|
|
|
5554 |
}
|
|
|
|
|
5555 |
}
|
5556 |
-
|
5557 |
|
5558 |
-
|
5559 |
-
|
5560 |
-
|
|
|
|
|
|
|
5561 |
|
5562 |
-
|
5563 |
-
|
5564 |
-
|
5565 |
-
|
|
|
5566 |
|
5567 |
-
|
5568 |
-
|
5569 |
-
|
5570 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5571 |
|
5572 |
-
|
5573 |
-
|
|
|
|
|
|
|
5574 |
|
5575 |
-
|
5576 |
-
foreach ($prototype as $i => $p) {
|
5577 |
-
$name = explode(':', $p)[0];
|
5578 |
|
5579 |
-
|
5580 |
-
|
5581 |
-
|
|
|
5582 |
|
5583 |
-
|
5584 |
-
|
5585 |
-
|
5586 |
-
|
5587 |
}
|
5588 |
-
// should we break if this prototype seems fulfilled?
|
5589 |
-
} catch (CompilerException $e) {
|
5590 |
-
$exceptionMessage = $e->getMessage();
|
5591 |
}
|
5592 |
-
|
|
|
|
|
5593 |
}
|
5594 |
|
5595 |
-
|
5596 |
-
|
5597 |
-
|
5598 |
-
|
5599 |
-
|
5600 |
-
|
5601 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5602 |
}
|
|
|
|
|
|
|
|
|
5603 |
}
|
|
|
5604 |
|
5605 |
-
|
|
|
5606 |
}
|
5607 |
|
5608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5609 |
}
|
5610 |
|
5611 |
/**
|
5612 |
-
*
|
5613 |
*
|
5614 |
-
* @param array
|
5615 |
-
* @param
|
5616 |
-
* @param
|
5617 |
-
* @param
|
5618 |
-
* only used if $storeInEnv = false
|
5619 |
*
|
5620 |
-
* @return
|
5621 |
*
|
5622 |
-
* @throws
|
|
|
|
|
5623 |
*/
|
5624 |
-
|
5625 |
{
|
5626 |
-
$
|
5627 |
|
5628 |
-
|
5629 |
-
|
5630 |
-
}
|
5631 |
|
5632 |
-
|
5633 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5634 |
|
5635 |
-
|
5636 |
-
|
5637 |
}
|
5638 |
|
5639 |
-
$
|
5640 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5641 |
|
5642 |
-
|
5643 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5644 |
|
5645 |
-
|
5646 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5647 |
}
|
5648 |
|
5649 |
-
$splatSeparator
|
5650 |
-
$keywordArgs
|
5651 |
-
$
|
5652 |
-
$
|
5653 |
-
$
|
5654 |
-
$
|
5655 |
|
5656 |
-
|
5657 |
-
|
5658 |
-
if (! empty($arg[0])) {
|
5659 |
$hasKeywordArgument = true;
|
5660 |
|
5661 |
-
$
|
|
|
5662 |
|
5663 |
-
if (
|
5664 |
-
|
5665 |
-
if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
|
5666 |
-
$name = $an;
|
5667 |
-
break;
|
5668 |
-
}
|
5669 |
-
}
|
5670 |
}
|
5671 |
|
5672 |
-
|
5673 |
-
|
5674 |
-
$deferredNamedKeywordArgs[$name] = $arg[1];
|
5675 |
-
} else {
|
5676 |
-
throw $this->error("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
|
5677 |
-
}
|
5678 |
-
} elseif ($args[$name][0] < \count($remaining)) {
|
5679 |
-
throw $this->error("The argument $%s was passed both by position and by name.", $arg[0][1]);
|
5680 |
-
} else {
|
5681 |
-
$keywordArgs[$name] = $arg[1];
|
5682 |
-
}
|
5683 |
} elseif (! empty($arg[2])) {
|
5684 |
// $arg[2] means a var followed by ... in the arg ($list... )
|
5685 |
$val = $this->reduce($arg[1], true);
|
|
|
5686 |
|
5687 |
if ($val[0] === Type::T_LIST) {
|
5688 |
-
foreach ($val[2] as $
|
5689 |
-
if (
|
5690 |
-
|
5691 |
-
|
5692 |
-
if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
|
5693 |
-
$name = $an;
|
5694 |
-
break;
|
5695 |
-
}
|
5696 |
-
}
|
5697 |
-
}
|
5698 |
|
5699 |
-
|
5700 |
-
|
5701 |
-
|
5702 |
-
|
5703 |
-
|
5704 |
-
|
5705 |
-
|
5706 |
-
|
|
|
|
|
|
|
5707 |
}
|
5708 |
|
5709 |
-
$
|
|
|
|
|
5710 |
}
|
5711 |
}
|
5712 |
} elseif ($val[0] === Type::T_MAP) {
|
@@ -5715,72 +6614,122 @@ class Compiler
|
|
5715 |
$item = $val[2][$i];
|
5716 |
|
5717 |
if (! is_numeric($name)) {
|
5718 |
-
|
5719 |
-
foreach (array_keys($args) as $an) {
|
5720 |
-
if (str_replace('_', '-', $an) === str_replace('_', '-', $name)) {
|
5721 |
-
$name = $an;
|
5722 |
-
break;
|
5723 |
-
}
|
5724 |
-
}
|
5725 |
-
}
|
5726 |
|
5727 |
-
if ($
|
5728 |
-
|
5729 |
-
} else {
|
5730 |
-
$keywordArgs[$name] = $item;
|
5731 |
}
|
|
|
|
|
|
|
|
|
5732 |
} else {
|
5733 |
if (\is_null($splatSeparator)) {
|
5734 |
$splatSeparator = $val[1];
|
5735 |
}
|
5736 |
|
5737 |
-
$
|
5738 |
}
|
5739 |
}
|
5740 |
-
}
|
5741 |
-
$
|
5742 |
}
|
5743 |
} elseif ($hasKeywordArgument) {
|
5744 |
-
throw
|
5745 |
} else {
|
5746 |
-
$
|
5747 |
}
|
5748 |
}
|
5749 |
|
5750 |
-
|
5751 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5752 |
|
5753 |
if ($isVariable) {
|
5754 |
-
|
5755 |
-
|
5756 |
-
|
5757 |
-
|
5758 |
-
|
5759 |
-
|
5760 |
-
}
|
5761 |
|
5762 |
-
|
5763 |
|
5764 |
-
|
5765 |
-
$val[2][] = $remaining[$i];
|
5766 |
-
}
|
5767 |
|
5768 |
-
|
5769 |
-
$val[2][$itemName] = $item;
|
5770 |
-
}
|
5771 |
|
5772 |
-
|
5773 |
-
|
5774 |
-
|
5775 |
-
|
5776 |
-
$val = $remaining[$i];
|
5777 |
-
} elseif (isset($keywordArgs[$name])) {
|
5778 |
-
$val = $keywordArgs[$name];
|
5779 |
-
} elseif (! empty($default)) {
|
5780 |
continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
5781 |
} else {
|
5782 |
-
|
5783 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
5784 |
|
5785 |
if ($storeInEnv) {
|
5786 |
$this->set($name, $this->reduce($val, true), true, $env);
|
@@ -5793,12 +6742,13 @@ class Compiler
|
|
5793 |
$storeEnv->store = $env->store;
|
5794 |
}
|
5795 |
|
5796 |
-
foreach ($
|
5797 |
-
list($
|
5798 |
|
5799 |
-
if (
|
5800 |
continue;
|
5801 |
}
|
|
|
5802 |
|
5803 |
if ($storeInEnv) {
|
5804 |
$this->set($name, $this->reduce($default, true), true);
|
@@ -5810,16 +6760,77 @@ class Compiler
|
|
5810 |
return $output;
|
5811 |
}
|
5812 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5813 |
/**
|
5814 |
* Coerce a php value into a scss one
|
5815 |
*
|
5816 |
* @param mixed $value
|
5817 |
*
|
5818 |
-
* @return array
|
5819 |
*/
|
5820 |
protected function coerceValue($value)
|
5821 |
{
|
5822 |
-
if (\is_array($value) || $value instanceof
|
5823 |
return $value;
|
5824 |
}
|
5825 |
|
@@ -5832,7 +6843,7 @@ class Compiler
|
|
5832 |
}
|
5833 |
|
5834 |
if (is_numeric($value)) {
|
5835 |
-
return new
|
5836 |
}
|
5837 |
|
5838 |
if ($value === '') {
|
@@ -5852,9 +6863,9 @@ class Compiler
|
|
5852 |
/**
|
5853 |
* Coerce something to map
|
5854 |
*
|
5855 |
-
* @param array $item
|
5856 |
*
|
5857 |
-
* @return array
|
5858 |
*/
|
5859 |
protected function coerceMap($item)
|
5860 |
{
|
@@ -5863,9 +6874,8 @@ class Compiler
|
|
5863 |
}
|
5864 |
|
5865 |
if (
|
5866 |
-
$item[0] ===
|
5867 |
-
$item[
|
5868 |
-
$item[2] === static::$emptyList[2]
|
5869 |
) {
|
5870 |
return static::$emptyMap;
|
5871 |
}
|
@@ -5876,15 +6886,19 @@ class Compiler
|
|
5876 |
/**
|
5877 |
* Coerce something to list
|
5878 |
*
|
5879 |
-
* @param array
|
5880 |
-
* @param string
|
5881 |
-
* @param boolean
|
5882 |
*
|
5883 |
* @return array
|
5884 |
*/
|
5885 |
protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
|
5886 |
{
|
5887 |
-
if (
|
|
|
|
|
|
|
|
|
5888 |
// remove trailing null from the list
|
5889 |
if ($removeTrailingNull && end($item[2]) === static::$null) {
|
5890 |
array_pop($item[2]);
|
@@ -5893,7 +6907,7 @@ class Compiler
|
|
5893 |
return $item;
|
5894 |
}
|
5895 |
|
5896 |
-
if (
|
5897 |
$keys = $item[1];
|
5898 |
$values = $item[2];
|
5899 |
$list = [];
|
@@ -5924,15 +6938,15 @@ class Compiler
|
|
5924 |
return [Type::T_LIST, ',', $list];
|
5925 |
}
|
5926 |
|
5927 |
-
return [Type::T_LIST, $delim,
|
5928 |
}
|
5929 |
|
5930 |
/**
|
5931 |
* Coerce color for expression
|
5932 |
*
|
5933 |
-
* @param array $value
|
5934 |
*
|
5935 |
-
* @return array|
|
5936 |
*/
|
5937 |
protected function coerceForExpression($value)
|
5938 |
{
|
@@ -5946,12 +6960,17 @@ class Compiler
|
|
5946 |
/**
|
5947 |
* Coerce value to color
|
5948 |
*
|
5949 |
-
* @param array $value
|
|
|
5950 |
*
|
5951 |
* @return array|null
|
5952 |
*/
|
5953 |
protected function coerceColor($value, $inRGBFunction = false)
|
5954 |
{
|
|
|
|
|
|
|
|
|
5955 |
switch ($value[0]) {
|
5956 |
case Type::T_COLOR:
|
5957 |
for ($i = 1; $i <= 3; $i++) {
|
@@ -6037,7 +7056,7 @@ class Compiler
|
|
6037 |
if ($color[3] === 255) {
|
6038 |
$color[3] = 1; // fully opaque
|
6039 |
} else {
|
6040 |
-
$color[3] = round($color[3] / 255,
|
6041 |
}
|
6042 |
}
|
6043 |
|
@@ -6060,8 +7079,8 @@ class Compiler
|
|
6060 |
}
|
6061 |
|
6062 |
/**
|
6063 |
-
* @param integer
|
6064 |
-
* @param boolean
|
6065 |
*
|
6066 |
* @return integer|mixed
|
6067 |
*/
|
@@ -6079,36 +7098,27 @@ class Compiler
|
|
6079 |
* @param integer|float $min
|
6080 |
* @param integer|float $max
|
6081 |
* @param boolean $isInt
|
6082 |
-
* @param boolean $clamp
|
6083 |
-
* @param boolean $modulo
|
6084 |
*
|
6085 |
* @return integer|mixed
|
6086 |
*/
|
6087 |
-
protected function compileColorPartValue($value, $min, $max, $isInt = true
|
6088 |
{
|
6089 |
if (! is_numeric($value)) {
|
6090 |
if (\is_array($value)) {
|
6091 |
$reduced = $this->reduce($value);
|
6092 |
|
6093 |
-
if (
|
6094 |
$value = $reduced;
|
6095 |
}
|
6096 |
}
|
6097 |
|
6098 |
-
if (
|
6099 |
-
|
6100 |
-
|
6101 |
-
|
6102 |
-
$
|
6103 |
-
|
6104 |
-
|
6105 |
-
switch ($unit) {
|
6106 |
-
case '%':
|
6107 |
-
$num *= $max / 100;
|
6108 |
-
break;
|
6109 |
-
default:
|
6110 |
-
break;
|
6111 |
-
}
|
6112 |
}
|
6113 |
|
6114 |
$value = $num;
|
@@ -6122,18 +7132,7 @@ class Compiler
|
|
6122 |
$value = round($value);
|
6123 |
}
|
6124 |
|
6125 |
-
|
6126 |
-
$value = min($max, max($min, $value));
|
6127 |
-
}
|
6128 |
-
|
6129 |
-
if ($modulo) {
|
6130 |
-
$value = $value % $max;
|
6131 |
-
|
6132 |
-
// still negative?
|
6133 |
-
while ($value < $min) {
|
6134 |
-
$value += $max;
|
6135 |
-
}
|
6136 |
-
}
|
6137 |
|
6138 |
return $value;
|
6139 |
}
|
@@ -6144,9 +7143,9 @@ class Compiler
|
|
6144 |
/**
|
6145 |
* Coerce value to string
|
6146 |
*
|
6147 |
-
* @param array $value
|
6148 |
*
|
6149 |
-
* @return array
|
6150 |
*/
|
6151 |
protected function coerceString($value)
|
6152 |
{
|
@@ -6158,16 +7157,21 @@ class Compiler
|
|
6158 |
}
|
6159 |
|
6160 |
/**
|
6161 |
-
* Assert value is a string
|
|
|
|
|
|
|
|
|
|
|
6162 |
*
|
6163 |
* @api
|
6164 |
*
|
6165 |
-
* @param array $value
|
6166 |
-
* @param string
|
6167 |
*
|
6168 |
* @return array
|
6169 |
*
|
6170 |
-
* @throws
|
6171 |
*/
|
6172 |
public function assertString($value, $varName = null)
|
6173 |
{
|
@@ -6178,30 +7182,27 @@ class Compiler
|
|
6178 |
|
6179 |
if (! \in_array($value[0], [Type::T_STRING, Type::T_KEYWORD])) {
|
6180 |
$value = $this->compileValue($value);
|
6181 |
-
|
6182 |
-
throw $this->error("Error:{$var_display} $value is not a string.");
|
6183 |
}
|
6184 |
|
6185 |
-
|
6186 |
-
|
6187 |
-
return $value;
|
6188 |
}
|
6189 |
|
6190 |
/**
|
6191 |
* Coerce value to a percentage
|
6192 |
*
|
6193 |
-
* @param array $value
|
6194 |
*
|
6195 |
* @return integer|float
|
6196 |
*/
|
6197 |
protected function coercePercent($value)
|
6198 |
{
|
6199 |
-
if ($value
|
6200 |
-
if (
|
6201 |
-
return $value
|
6202 |
}
|
6203 |
|
6204 |
-
return $value
|
6205 |
}
|
6206 |
|
6207 |
return 0;
|
@@ -6212,18 +7213,21 @@ class Compiler
|
|
6212 |
*
|
6213 |
* @api
|
6214 |
*
|
6215 |
-
* @param array $value
|
|
|
6216 |
*
|
6217 |
* @return array
|
6218 |
*
|
6219 |
-
* @throws
|
6220 |
*/
|
6221 |
-
public function assertMap($value)
|
6222 |
{
|
6223 |
$value = $this->coerceMap($value);
|
6224 |
|
6225 |
if ($value[0] !== Type::T_MAP) {
|
6226 |
-
|
|
|
|
|
6227 |
}
|
6228 |
|
6229 |
return $value;
|
@@ -6234,7 +7238,7 @@ class Compiler
|
|
6234 |
*
|
6235 |
* @api
|
6236 |
*
|
6237 |
-
* @param array $value
|
6238 |
*
|
6239 |
* @return array
|
6240 |
*
|
@@ -6249,24 +7253,48 @@ class Compiler
|
|
6249 |
return $value;
|
6250 |
}
|
6251 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6252 |
/**
|
6253 |
* Assert value is a color
|
6254 |
*
|
6255 |
* @api
|
6256 |
*
|
6257 |
-
* @param array $value
|
|
|
6258 |
*
|
6259 |
* @return array
|
6260 |
*
|
6261 |
-
* @throws
|
6262 |
*/
|
6263 |
-
public function assertColor($value)
|
6264 |
{
|
6265 |
if ($color = $this->coerceColor($value)) {
|
6266 |
return $color;
|
6267 |
}
|
6268 |
|
6269 |
-
|
|
|
|
|
6270 |
}
|
6271 |
|
6272 |
/**
|
@@ -6274,22 +7302,21 @@ class Compiler
|
|
6274 |
*
|
6275 |
* @api
|
6276 |
*
|
6277 |
-
* @param array $value
|
6278 |
-
* @param string
|
6279 |
*
|
6280 |
-
* @return
|
6281 |
*
|
6282 |
-
* @throws
|
6283 |
*/
|
6284 |
public function assertNumber($value, $varName = null)
|
6285 |
{
|
6286 |
-
if (
|
6287 |
$value = $this->compileValue($value);
|
6288 |
-
|
6289 |
-
throw $this->error("Error:{$var_display} $value is not a number.");
|
6290 |
}
|
6291 |
|
6292 |
-
return $value
|
6293 |
}
|
6294 |
|
6295 |
/**
|
@@ -6297,25 +7324,41 @@ class Compiler
|
|
6297 |
*
|
6298 |
* @api
|
6299 |
*
|
6300 |
-
* @param array $value
|
6301 |
-
* @param string
|
6302 |
*
|
6303 |
-
* @return integer
|
6304 |
*
|
6305 |
-
* @throws
|
6306 |
*/
|
6307 |
public function assertInteger($value, $varName = null)
|
6308 |
{
|
6309 |
-
|
6310 |
-
$value
|
6311 |
-
|
6312 |
-
$var_display = ($varName ? " \${$varName}:" : '');
|
6313 |
-
throw $this->error("Error:{$var_display} $value is not an integer.");
|
6314 |
}
|
6315 |
|
6316 |
return intval($value);
|
6317 |
}
|
6318 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6319 |
|
6320 |
/**
|
6321 |
* Make sure a color's components don't go out of bounds
|
@@ -6334,6 +7377,10 @@ class Compiler
|
|
6334 |
if ($c[$i] > 255) {
|
6335 |
$c[$i] = 255;
|
6336 |
}
|
|
|
|
|
|
|
|
|
6337 |
}
|
6338 |
|
6339 |
return $c;
|
@@ -6342,7 +7389,7 @@ class Compiler
|
|
6342 |
/**
|
6343 |
* Convert RGB to HSL
|
6344 |
*
|
6345 |
-
* @
|
6346 |
*
|
6347 |
* @param integer $red
|
6348 |
* @param integer $green
|
@@ -6414,11 +7461,11 @@ class Compiler
|
|
6414 |
/**
|
6415 |
* Convert HSL to RGB
|
6416 |
*
|
6417 |
-
* @
|
6418 |
*
|
6419 |
-
* @param
|
6420 |
-
* @param
|
6421 |
-
* @param
|
6422 |
*
|
6423 |
* @return array
|
6424 |
*/
|
@@ -6444,19 +7491,87 @@ class Compiler
|
|
6444 |
return $out;
|
6445 |
}
|
6446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6447 |
// Built in functions
|
6448 |
|
6449 |
protected static $libCall = ['function', 'args...'];
|
6450 |
-
protected function libCall($args
|
6451 |
{
|
6452 |
-
$functionReference = $
|
6453 |
|
6454 |
if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
|
6455 |
-
$name = $this->compileStringContent($this->coerceString($
|
6456 |
-
$warning = "
|
6457 |
. "in Sass 4.0. Use call(function-reference($name)) instead.";
|
6458 |
-
|
6459 |
-
$functionReference = $this->libGetFunction([$functionReference]);
|
6460 |
}
|
6461 |
|
6462 |
if ($functionReference === static::$null) {
|
@@ -6467,18 +7582,9 @@ class Compiler
|
|
6467 |
throw $this->error('Function reference expected, got ' . $functionReference[0]);
|
6468 |
}
|
6469 |
|
6470 |
-
$callArgs = [
|
6471 |
-
|
6472 |
-
|
6473 |
-
foreach ($kwargs['args'][2] as $varname => $arg) {
|
6474 |
-
if (is_numeric($varname)) {
|
6475 |
-
$varname = null;
|
6476 |
-
} else {
|
6477 |
-
$varname = [ 'var', $varname];
|
6478 |
-
}
|
6479 |
-
|
6480 |
-
$callArgs[] = [$varname, $arg, false];
|
6481 |
-
}
|
6482 |
|
6483 |
return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
|
6484 |
}
|
@@ -6490,11 +7596,11 @@ class Compiler
|
|
6490 |
];
|
6491 |
protected function libGetFunction($args)
|
6492 |
{
|
6493 |
-
$name = $this->compileStringContent($this->
|
6494 |
$isCss = false;
|
6495 |
|
6496 |
if (count($args)) {
|
6497 |
-
$isCss =
|
6498 |
$isCss = (($isCss === static::$true) ? true : false);
|
6499 |
}
|
6500 |
|
@@ -6535,15 +7641,32 @@ class Compiler
|
|
6535 |
return static::$null;
|
6536 |
}
|
6537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6538 |
$values = [];
|
6539 |
|
|
|
6540 |
foreach ($list[2] as $item) {
|
6541 |
$values[] = $this->normalizeValue($item);
|
6542 |
}
|
6543 |
|
6544 |
$key = array_search($this->normalizeValue($value), $values);
|
6545 |
|
6546 |
-
return false === $key ? static::$null : $key + 1;
|
6547 |
}
|
6548 |
|
6549 |
protected static $libRgb = [
|
@@ -6610,32 +7733,133 @@ class Compiler
|
|
6610 |
return $this->libRgb($args, $kwargs, 'rgba');
|
6611 |
}
|
6612 |
|
6613 |
-
|
6614 |
-
|
6615 |
-
|
6616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6617 |
|
6618 |
-
|
6619 |
-
|
6620 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6621 |
|
6622 |
-
|
6623 |
-
|
6624 |
-
|
|
|
6625 |
|
6626 |
-
|
6627 |
-
|
6628 |
}
|
6629 |
|
6630 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6631 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
6632 |
|
6633 |
-
|
6634 |
-
|
6635 |
-
$val = $this->assertNumber($args[$iarg]);
|
6636 |
-
$hsl[$ihsl] = \call_user_func($fn, $hsl[$ihsl], $val, $iarg);
|
6637 |
-
}
|
6638 |
}
|
|
|
|
|
6639 |
|
6640 |
$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
|
6641 |
|
@@ -6646,58 +7870,54 @@ class Compiler
|
|
6646 |
$color = $rgb;
|
6647 |
}
|
6648 |
|
|
|
|
|
|
|
|
|
|
|
6649 |
return $color;
|
6650 |
}
|
6651 |
|
6652 |
-
protected static $libAdjustColor = [
|
6653 |
-
'color', 'red:null', 'green:null', 'blue:null',
|
6654 |
-
'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
|
6655 |
-
];
|
6656 |
protected function libAdjustColor($args)
|
6657 |
{
|
6658 |
-
return $this->alterColor($args, function ($base, $alter, $
|
6659 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6660 |
});
|
6661 |
}
|
6662 |
|
6663 |
-
protected static $libChangeColor = [
|
6664 |
-
'color', 'red:null', 'green:null', 'blue:null',
|
6665 |
-
'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
|
6666 |
-
];
|
6667 |
protected function libChangeColor($args)
|
6668 |
{
|
6669 |
-
return $this->alterColor($args, function ($base, $alter, $
|
|
|
|
|
|
|
|
|
6670 |
return $alter;
|
6671 |
});
|
6672 |
}
|
6673 |
|
6674 |
-
protected static $libScaleColor = [
|
6675 |
-
'color', 'red:null', 'green:null', 'blue:null',
|
6676 |
-
'hue:null', 'saturation:null', 'lightness:null', 'alpha:null'
|
6677 |
-
];
|
6678 |
protected function libScaleColor($args)
|
6679 |
{
|
6680 |
-
return $this->alterColor($args, function ($base, $scale, $
|
6681 |
-
|
6682 |
-
|
6683 |
-
// 7 - a
|
6684 |
-
switch ($i) {
|
6685 |
-
case 1:
|
6686 |
-
case 2:
|
6687 |
-
case 3:
|
6688 |
-
$max = 255;
|
6689 |
-
break;
|
6690 |
-
|
6691 |
-
case 4:
|
6692 |
-
$max = 360;
|
6693 |
-
break;
|
6694 |
-
|
6695 |
-
case 7:
|
6696 |
-
$max = 1;
|
6697 |
-
break;
|
6698 |
-
|
6699 |
-
default:
|
6700 |
-
$max = 100;
|
6701 |
}
|
6702 |
|
6703 |
$scale = $scale / 100;
|
@@ -6716,7 +7936,7 @@ class Compiler
|
|
6716 |
$color = $this->coerceColor($args[0]);
|
6717 |
|
6718 |
if (\is_null($color)) {
|
6719 |
-
$this->
|
6720 |
}
|
6721 |
|
6722 |
$color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
|
@@ -6730,10 +7950,10 @@ class Compiler
|
|
6730 |
$color = $this->coerceColor($args[0]);
|
6731 |
|
6732 |
if (\is_null($color)) {
|
6733 |
-
$this->
|
6734 |
}
|
6735 |
|
6736 |
-
return $color[1];
|
6737 |
}
|
6738 |
|
6739 |
protected static $libGreen = ['color'];
|
@@ -6742,10 +7962,10 @@ class Compiler
|
|
6742 |
$color = $this->coerceColor($args[0]);
|
6743 |
|
6744 |
if (\is_null($color)) {
|
6745 |
-
$this->
|
6746 |
}
|
6747 |
|
6748 |
-
return $color[2];
|
6749 |
}
|
6750 |
|
6751 |
protected static $libBlue = ['color'];
|
@@ -6754,17 +7974,17 @@ class Compiler
|
|
6754 |
$color = $this->coerceColor($args[0]);
|
6755 |
|
6756 |
if (\is_null($color)) {
|
6757 |
-
$this->
|
6758 |
}
|
6759 |
|
6760 |
-
return $color[3];
|
6761 |
}
|
6762 |
|
6763 |
protected static $libAlpha = ['color'];
|
6764 |
protected function libAlpha($args)
|
6765 |
{
|
6766 |
if ($color = $this->coerceColor($args[0])) {
|
6767 |
-
return isset($color[4]) ? $color[4] : 1;
|
6768 |
}
|
6769 |
|
6770 |
// this might be the IE function, so return value unchanged
|
@@ -6776,7 +7996,7 @@ class Compiler
|
|
6776 |
{
|
6777 |
$value = $args[0];
|
6778 |
|
6779 |
-
if ($value
|
6780 |
return null;
|
6781 |
}
|
6782 |
|
@@ -6792,14 +8012,9 @@ class Compiler
|
|
6792 |
{
|
6793 |
list($first, $second, $weight) = $args;
|
6794 |
|
6795 |
-
$first = $this->assertColor($first);
|
6796 |
-
$second = $this->assertColor($second);
|
6797 |
-
|
6798 |
-
if (! isset($weight)) {
|
6799 |
-
$weight = 0.5;
|
6800 |
-
} else {
|
6801 |
-
$weight = $this->coercePercent($weight);
|
6802 |
-
}
|
6803 |
|
6804 |
$firstAlpha = isset($first[4]) ? $first[4] : 1;
|
6805 |
$secondAlpha = isset($second[4]) ? $second[4] : 1;
|
@@ -6825,6 +8040,7 @@ class Compiler
|
|
6825 |
|
6826 |
protected static $libHsl = [
|
6827 |
['channels'],
|
|
|
6828 |
['hue', 'saturation', 'lightness'],
|
6829 |
['hue', 'saturation', 'lightness', 'alpha'] ];
|
6830 |
protected function libHsl($args, $kwargs, $funcName = 'hsl')
|
@@ -6840,24 +8056,30 @@ class Compiler
|
|
6840 |
$args_to_check = $kwargs['channels'][2];
|
6841 |
}
|
6842 |
|
6843 |
-
|
6844 |
-
|
6845 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6846 |
|
6847 |
foreach ($kwargs as $k => $arg) {
|
6848 |
-
if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
|
6849 |
return null;
|
6850 |
}
|
6851 |
}
|
6852 |
|
6853 |
foreach ($args_to_check as $k => $arg) {
|
6854 |
-
if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
|
6855 |
if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
|
6856 |
return null;
|
6857 |
}
|
6858 |
|
6859 |
$args[$k] = $this->stringifyFncallArgs($arg);
|
6860 |
-
$hue = '';
|
6861 |
}
|
6862 |
|
6863 |
if (
|
@@ -6869,22 +8091,31 @@ class Compiler
|
|
6869 |
}
|
6870 |
}
|
6871 |
|
|
|
|
|
|
|
6872 |
$alpha = null;
|
6873 |
|
6874 |
if (\count($args) === 4) {
|
6875 |
$alpha = $this->compileColorPartValue($args[3], 0, 100, false);
|
6876 |
|
6877 |
-
if (
|
6878 |
return [Type::T_STRING, '',
|
6879 |
[$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
|
6880 |
}
|
6881 |
} else {
|
6882 |
-
if (
|
6883 |
return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
|
6884 |
}
|
6885 |
}
|
6886 |
|
6887 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
6888 |
|
6889 |
if (! \is_null($alpha)) {
|
6890 |
$color[4] = $alpha;
|
@@ -6895,6 +8126,7 @@ class Compiler
|
|
6895 |
|
6896 |
protected static $libHsla = [
|
6897 |
['channels'],
|
|
|
6898 |
['hue', 'saturation', 'lightness'],
|
6899 |
['hue', 'saturation', 'lightness', 'alpha']];
|
6900 |
protected function libHsla($args, $kwargs)
|
@@ -6905,30 +8137,156 @@ class Compiler
|
|
6905 |
protected static $libHue = ['color'];
|
6906 |
protected function libHue($args)
|
6907 |
{
|
6908 |
-
$color = $this->assertColor($args[0]);
|
6909 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
6910 |
|
6911 |
-
return new
|
6912 |
}
|
6913 |
|
6914 |
protected static $libSaturation = ['color'];
|
6915 |
protected function libSaturation($args)
|
6916 |
{
|
6917 |
-
$color = $this->assertColor($args[0]);
|
6918 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
6919 |
|
6920 |
-
return new
|
6921 |
}
|
6922 |
|
6923 |
protected static $libLightness = ['color'];
|
6924 |
protected function libLightness($args)
|
6925 |
{
|
6926 |
-
$color = $this->assertColor($args[0]);
|
6927 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
6928 |
|
6929 |
-
return new
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6930 |
}
|
6931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6932 |
protected function adjustHsl($color, $idx, $amount)
|
6933 |
{
|
6934 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
@@ -6945,8 +8303,8 @@ class Compiler
|
|
6945 |
protected static $libAdjustHue = ['color', 'degrees'];
|
6946 |
protected function libAdjustHue($args)
|
6947 |
{
|
6948 |
-
$color = $this->assertColor($args[0]);
|
6949 |
-
$degrees = $this->assertNumber($args[1]);
|
6950 |
|
6951 |
return $this->adjustHsl($color, 1, $degrees);
|
6952 |
}
|
@@ -6954,7 +8312,7 @@ class Compiler
|
|
6954 |
protected static $libLighten = ['color', 'amount'];
|
6955 |
protected function libLighten($args)
|
6956 |
{
|
6957 |
-
$color = $this->assertColor($args[0]);
|
6958 |
$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
|
6959 |
|
6960 |
return $this->adjustHsl($color, 3, $amount);
|
@@ -6963,7 +8321,7 @@ class Compiler
|
|
6963 |
protected static $libDarken = ['color', 'amount'];
|
6964 |
protected function libDarken($args)
|
6965 |
{
|
6966 |
-
$color = $this->assertColor($args[0]);
|
6967 |
$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
|
6968 |
|
6969 |
return $this->adjustHsl($color, 3, -$amount);
|
@@ -6974,17 +8332,14 @@ class Compiler
|
|
6974 |
{
|
6975 |
$value = $args[0];
|
6976 |
|
6977 |
-
if ($value[0] === Type::T_NUMBER) {
|
6978 |
-
return null;
|
6979 |
-
}
|
6980 |
-
|
6981 |
if (count($args) === 1) {
|
6982 |
-
$
|
6983 |
-
|
|
|
6984 |
}
|
6985 |
|
6986 |
-
$color = $this->assertColor($value);
|
6987 |
-
$amount = 100 * $this->coercePercent($args[1]);
|
6988 |
|
6989 |
return $this->adjustHsl($color, 2, $amount);
|
6990 |
}
|
@@ -6992,8 +8347,8 @@ class Compiler
|
|
6992 |
protected static $libDesaturate = ['color', 'amount'];
|
6993 |
protected function libDesaturate($args)
|
6994 |
{
|
6995 |
-
$color = $this->assertColor($args[0]);
|
6996 |
-
$amount = 100 * $this->coercePercent($args[1]);
|
6997 |
|
6998 |
return $this->adjustHsl($color, 2, -$amount);
|
6999 |
}
|
@@ -7003,42 +8358,38 @@ class Compiler
|
|
7003 |
{
|
7004 |
$value = $args[0];
|
7005 |
|
7006 |
-
if ($value
|
7007 |
return null;
|
7008 |
}
|
7009 |
|
7010 |
-
return $this->adjustHsl($this->assertColor($value), 2, -100);
|
7011 |
}
|
7012 |
|
7013 |
protected static $libComplement = ['color'];
|
7014 |
protected function libComplement($args)
|
7015 |
{
|
7016 |
-
return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
|
7017 |
}
|
7018 |
|
7019 |
protected static $libInvert = ['color', 'weight:1'];
|
7020 |
protected function libInvert($args)
|
7021 |
{
|
7022 |
-
|
7023 |
-
|
7024 |
-
if (! isset($weight)) {
|
7025 |
-
$weight = 1;
|
7026 |
-
} else {
|
7027 |
-
$weight = $this->coercePercent($weight);
|
7028 |
-
}
|
7029 |
|
7030 |
-
if ($value
|
7031 |
return null;
|
7032 |
}
|
7033 |
|
7034 |
-
$
|
|
|
|
|
7035 |
$inverted = $color;
|
7036 |
$inverted[1] = 255 - $inverted[1];
|
7037 |
$inverted[2] = 255 - $inverted[2];
|
7038 |
$inverted[3] = 255 - $inverted[3];
|
7039 |
|
7040 |
if ($weight < 1) {
|
7041 |
-
return $this->libMix([$inverted, $color,
|
7042 |
}
|
7043 |
|
7044 |
return $inverted;
|
@@ -7048,8 +8399,8 @@ class Compiler
|
|
7048 |
protected static $libOpacify = ['color', 'amount'];
|
7049 |
protected function libOpacify($args)
|
7050 |
{
|
7051 |
-
$color = $this->assertColor($args[0]);
|
7052 |
-
$amount = $this->coercePercent($args[1]);
|
7053 |
|
7054 |
$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
|
7055 |
$color[4] = min(1, max(0, $color[4]));
|
@@ -7067,8 +8418,8 @@ class Compiler
|
|
7067 |
protected static $libTransparentize = ['color', 'amount'];
|
7068 |
protected function libTransparentize($args)
|
7069 |
{
|
7070 |
-
$color = $this->assertColor($args[0]);
|
7071 |
-
$amount = $this->coercePercent($args[1]);
|
7072 |
|
7073 |
$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
|
7074 |
$color[4] = min(1, max(0, $color[4]));
|
@@ -7085,140 +8436,121 @@ class Compiler
|
|
7085 |
protected static $libUnquote = ['string'];
|
7086 |
protected function libUnquote($args)
|
7087 |
{
|
7088 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7089 |
|
7090 |
-
|
7091 |
-
|
|
|
7092 |
}
|
7093 |
|
|
|
|
|
7094 |
return $str;
|
7095 |
}
|
7096 |
|
7097 |
protected static $libQuote = ['string'];
|
7098 |
protected function libQuote($args)
|
7099 |
{
|
7100 |
-
$value = $args[0];
|
7101 |
|
7102 |
-
|
7103 |
-
return $value;
|
7104 |
-
}
|
7105 |
|
7106 |
-
return
|
7107 |
}
|
7108 |
|
7109 |
protected static $libPercentage = ['number'];
|
7110 |
protected function libPercentage($args)
|
7111 |
{
|
7112 |
-
|
|
|
|
|
|
|
7113 |
}
|
7114 |
|
7115 |
protected static $libRound = ['number'];
|
7116 |
protected function libRound($args)
|
7117 |
{
|
7118 |
-
$num = $args[0];
|
7119 |
|
7120 |
-
return new
|
7121 |
}
|
7122 |
|
7123 |
protected static $libFloor = ['number'];
|
7124 |
protected function libFloor($args)
|
7125 |
{
|
7126 |
-
$num = $args[0];
|
7127 |
|
7128 |
-
return new
|
7129 |
}
|
7130 |
|
7131 |
protected static $libCeil = ['number'];
|
7132 |
protected function libCeil($args)
|
7133 |
{
|
7134 |
-
$num = $args[0];
|
7135 |
|
7136 |
-
return new
|
7137 |
}
|
7138 |
|
7139 |
protected static $libAbs = ['number'];
|
7140 |
protected function libAbs($args)
|
7141 |
{
|
7142 |
-
$num = $args[0];
|
7143 |
|
7144 |
-
return new
|
7145 |
}
|
7146 |
|
|
|
7147 |
protected function libMin($args)
|
7148 |
{
|
7149 |
-
|
7150 |
-
|
7151 |
-
|
|
|
7152 |
|
7153 |
-
foreach ($
|
7154 |
-
|
7155 |
|
7156 |
-
if (\is_null($
|
7157 |
-
|
7158 |
-
$minOriginal = $original;
|
7159 |
-
$minNormalized = $normalized;
|
7160 |
-
}
|
7161 |
-
} elseif ($normalized[1] <= $minNormalized[1]) {
|
7162 |
-
$minOriginal = $original;
|
7163 |
-
$minNormalized = $normalized;
|
7164 |
}
|
7165 |
}
|
7166 |
|
7167 |
-
|
7168 |
-
|
7169 |
-
|
7170 |
-
protected function libMax($args)
|
7171 |
-
{
|
7172 |
-
$numbers = $this->getNormalizedNumbers($args);
|
7173 |
-
$maxOriginal = null;
|
7174 |
-
$maxNormalized = null;
|
7175 |
-
|
7176 |
-
foreach ($numbers as $key => $pair) {
|
7177 |
-
list($original, $normalized) = $pair;
|
7178 |
-
|
7179 |
-
if (\is_null($normalized) || \is_null($maxNormalized)) {
|
7180 |
-
if (\is_null($maxOriginal) || $original[1] >= $maxOriginal[1]) {
|
7181 |
-
$maxOriginal = $original;
|
7182 |
-
$maxNormalized = $normalized;
|
7183 |
-
}
|
7184 |
-
} elseif ($normalized[1] >= $maxNormalized[1]) {
|
7185 |
-
$maxOriginal = $original;
|
7186 |
-
$maxNormalized = $normalized;
|
7187 |
-
}
|
7188 |
}
|
7189 |
|
7190 |
-
|
7191 |
}
|
7192 |
|
7193 |
-
|
7194 |
-
|
7195 |
-
*
|
7196 |
-
* @param array $args
|
7197 |
-
*
|
7198 |
-
* @return array
|
7199 |
-
*/
|
7200 |
-
protected function getNormalizedNumbers($args)
|
7201 |
{
|
7202 |
-
|
7203 |
-
|
7204 |
-
|
7205 |
-
|
7206 |
-
foreach ($args as $key => $item) {
|
7207 |
-
$this->assertNumber($item);
|
7208 |
|
7209 |
-
|
|
|
7210 |
|
7211 |
-
if (
|
7212 |
-
$
|
7213 |
-
$originalUnit = $item->unitStr();
|
7214 |
-
} elseif ($number[1] && $unit !== $number[2] && ! empty($number[2])) {
|
7215 |
-
throw $this->error('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
|
7216 |
}
|
|
|
7217 |
|
7218 |
-
|
|
|
7219 |
}
|
7220 |
|
7221 |
-
|
7222 |
}
|
7223 |
|
7224 |
protected static $libLength = ['list'];
|
@@ -7226,38 +8558,34 @@ class Compiler
|
|
7226 |
{
|
7227 |
$list = $this->coerceList($args[0], ',', true);
|
7228 |
|
7229 |
-
return \count($list[2]);
|
7230 |
}
|
7231 |
|
7232 |
-
|
7233 |
protected function libListSeparator($args)
|
7234 |
{
|
7235 |
-
if (\count($args) > 1) {
|
7236 |
-
return 'comma';
|
7237 |
-
}
|
7238 |
-
|
7239 |
if (! \in_array($args[0][0], [Type::T_LIST, Type::T_MAP])) {
|
7240 |
-
return 'space';
|
7241 |
}
|
7242 |
|
7243 |
$list = $this->coerceList($args[0]);
|
7244 |
|
7245 |
if (\count($list[2]) <= 1 && empty($list['enclosing'])) {
|
7246 |
-
return 'space';
|
7247 |
}
|
7248 |
|
7249 |
if ($list[1] === ',') {
|
7250 |
-
return 'comma';
|
7251 |
}
|
7252 |
|
7253 |
-
return 'space';
|
7254 |
}
|
7255 |
|
7256 |
protected static $libNth = ['list', 'n'];
|
7257 |
protected function libNth($args)
|
7258 |
{
|
7259 |
$list = $this->coerceList($args[0], ',', false);
|
7260 |
-
$n = $this->assertNumber($args[1]);
|
7261 |
|
7262 |
if ($n > 0) {
|
7263 |
$n--;
|
@@ -7272,7 +8600,7 @@ class Compiler
|
|
7272 |
protected function libSetNth($args)
|
7273 |
{
|
7274 |
$list = $this->coerceList($args[0]);
|
7275 |
-
$n = $this->assertNumber($args[1]);
|
7276 |
|
7277 |
if ($n > 0) {
|
7278 |
$n--;
|
@@ -7292,7 +8620,7 @@ class Compiler
|
|
7292 |
protected static $libMapGet = ['map', 'key'];
|
7293 |
protected function libMapGet($args)
|
7294 |
{
|
7295 |
-
$map = $this->assertMap($args[0]);
|
7296 |
$key = $args[1];
|
7297 |
|
7298 |
if (! \is_null($key)) {
|
@@ -7311,7 +8639,7 @@ class Compiler
|
|
7311 |
protected static $libMapKeys = ['map'];
|
7312 |
protected function libMapKeys($args)
|
7313 |
{
|
7314 |
-
$map = $this->assertMap($args[0]);
|
7315 |
$keys = $map[1];
|
7316 |
|
7317 |
return [Type::T_LIST, ',', $keys];
|
@@ -7320,21 +8648,28 @@ class Compiler
|
|
7320 |
protected static $libMapValues = ['map'];
|
7321 |
protected function libMapValues($args)
|
7322 |
{
|
7323 |
-
$map = $this->assertMap($args[0]);
|
7324 |
$values = $map[2];
|
7325 |
|
7326 |
return [Type::T_LIST, ',', $values];
|
7327 |
}
|
7328 |
|
7329 |
-
protected static $libMapRemove = [
|
|
|
|
|
|
|
7330 |
protected function libMapRemove($args)
|
7331 |
{
|
7332 |
-
$map = $this->assertMap($args[0]);
|
7333 |
-
|
|
|
|
|
|
|
7334 |
|
7335 |
$keys = [];
|
|
|
7336 |
|
7337 |
-
foreach ($
|
7338 |
$keys[] = $this->compileStringContent($this->coerceString($key));
|
7339 |
}
|
7340 |
|
@@ -7351,8 +8686,19 @@ class Compiler
|
|
7351 |
protected static $libMapHasKey = ['map', 'key'];
|
7352 |
protected function libMapHasKey($args)
|
7353 |
{
|
7354 |
-
$map = $this->assertMap($args[0]);
|
7355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7356 |
|
7357 |
for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
|
7358 |
if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
|
@@ -7369,8 +8715,8 @@ class Compiler
|
|
7369 |
];
|
7370 |
protected function libMapMerge($args)
|
7371 |
{
|
7372 |
-
$map1 = $this->assertMap($args[0]);
|
7373 |
-
$map2 = $this->assertMap($args[1]);
|
7374 |
|
7375 |
foreach ($map2[1] as $i2 => $key2) {
|
7376 |
$key = $this->compileStringContent($this->coerceString($key2));
|
@@ -7392,12 +8738,18 @@ class Compiler
|
|
7392 |
protected static $libKeywords = ['args'];
|
7393 |
protected function libKeywords($args)
|
7394 |
{
|
7395 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
7396 |
|
7397 |
$keys = [];
|
7398 |
$values = [];
|
7399 |
|
7400 |
-
foreach ($
|
7401 |
$keys[] = [Type::T_KEYWORD, $name];
|
7402 |
$values[] = $arg;
|
7403 |
}
|
@@ -7412,12 +8764,19 @@ class Compiler
|
|
7412 |
$this->coerceList($list, ' ');
|
7413 |
|
7414 |
if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
|
7415 |
-
return true;
|
7416 |
}
|
7417 |
|
7418 |
-
return false;
|
7419 |
}
|
7420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7421 |
protected function listSeparatorForJoin($list1, $sep)
|
7422 |
{
|
7423 |
if (! isset($sep)) {
|
@@ -7499,21 +8858,23 @@ class Compiler
|
|
7499 |
return $res;
|
7500 |
}
|
7501 |
|
|
|
7502 |
protected function libZip($args)
|
7503 |
{
|
7504 |
-
|
7505 |
-
|
|
|
7506 |
}
|
7507 |
|
7508 |
$lists = [];
|
7509 |
-
$firstList = array_shift($
|
7510 |
|
7511 |
$result = [Type::T_LIST, ',', $lists];
|
7512 |
if (! \is_null($firstList)) {
|
7513 |
foreach ($firstList[2] as $key => $item) {
|
7514 |
$list = [Type::T_LIST, '', [$item]];
|
7515 |
|
7516 |
-
foreach ($
|
7517 |
if (isset($arg[2][$key])) {
|
7518 |
$list[2][] = $arg[2][$key];
|
7519 |
} else {
|
@@ -7537,6 +8898,16 @@ class Compiler
|
|
7537 |
{
|
7538 |
$value = $args[0];
|
7539 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7540 |
switch ($value[0]) {
|
7541 |
case Type::T_KEYWORD:
|
7542 |
if ($value === static::$true || $value === static::$false) {
|
@@ -7555,7 +8926,7 @@ class Compiler
|
|
7555 |
return 'function';
|
7556 |
|
7557 |
case Type::T_LIST:
|
7558 |
-
if (isset($value[3]) && $value[3]) {
|
7559 |
return 'arglist';
|
7560 |
}
|
7561 |
|
@@ -7568,21 +8939,17 @@ class Compiler
|
|
7568 |
protected static $libUnit = ['number'];
|
7569 |
protected function libUnit($args)
|
7570 |
{
|
7571 |
-
$num = $args[0];
|
7572 |
-
|
7573 |
-
if ($num[0] === Type::T_NUMBER) {
|
7574 |
-
return [Type::T_STRING, '"', [$num->unitStr()]];
|
7575 |
-
}
|
7576 |
|
7577 |
-
return '';
|
7578 |
}
|
7579 |
|
7580 |
protected static $libUnitless = ['number'];
|
7581 |
protected function libUnitless($args)
|
7582 |
{
|
7583 |
-
$value = $args[0];
|
7584 |
|
7585 |
-
return $
|
7586 |
}
|
7587 |
|
7588 |
protected static $libComparable = [
|
@@ -7594,16 +8961,13 @@ class Compiler
|
|
7594 |
list($number1, $number2) = $args;
|
7595 |
|
7596 |
if (
|
7597 |
-
!
|
7598 |
-
!
|
7599 |
) {
|
7600 |
throw $this->error('Invalid argument(s) for "comparable"');
|
7601 |
}
|
7602 |
|
7603 |
-
|
7604 |
-
$number2 = $number2->normalize();
|
7605 |
-
|
7606 |
-
return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
|
7607 |
}
|
7608 |
|
7609 |
protected static $libStrIndex = ['string', 'substring'];
|
@@ -7618,10 +8982,10 @@ class Compiler
|
|
7618 |
if (! \strlen($substringContent)) {
|
7619 |
$result = 0;
|
7620 |
} else {
|
7621 |
-
$result =
|
7622 |
}
|
7623 |
|
7624 |
-
return $result === false ? static::$null : new
|
7625 |
}
|
7626 |
|
7627 |
protected static $libStrInsert = ['string', 'insert', 'index'];
|
@@ -7656,31 +9020,43 @@ class Compiler
|
|
7656 |
$string = $this->assertString($args[0], 'string');
|
7657 |
$stringContent = $this->compileStringContent($string);
|
7658 |
|
7659 |
-
return new
|
7660 |
}
|
7661 |
|
7662 |
protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
|
7663 |
protected function libStrSlice($args)
|
7664 |
{
|
7665 |
-
|
7666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7667 |
}
|
7668 |
|
7669 |
-
$
|
7670 |
-
|
|
|
7671 |
|
7672 |
-
$
|
|
|
|
|
|
|
|
|
7673 |
|
7674 |
-
if ($
|
7675 |
-
$
|
7676 |
}
|
7677 |
|
7678 |
-
$
|
7679 |
-
$length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
|
7680 |
|
7681 |
-
$string[2] = $length
|
7682 |
-
? [substr($stringContent, $start, $length)]
|
7683 |
-
: [substr($stringContent, $start)];
|
7684 |
|
7685 |
return $string;
|
7686 |
}
|
@@ -7688,10 +9064,10 @@ class Compiler
|
|
7688 |
protected static $libToLowerCase = ['string'];
|
7689 |
protected function libToLowerCase($args)
|
7690 |
{
|
7691 |
-
$string = $this->
|
7692 |
$stringContent = $this->compileStringContent($string);
|
7693 |
|
7694 |
-
$string[2] = [
|
7695 |
|
7696 |
return $string;
|
7697 |
}
|
@@ -7699,18 +9075,45 @@ class Compiler
|
|
7699 |
protected static $libToUpperCase = ['string'];
|
7700 |
protected function libToUpperCase($args)
|
7701 |
{
|
7702 |
-
$string = $this->
|
7703 |
$stringContent = $this->compileStringContent($string);
|
7704 |
|
7705 |
-
$string[2] = [
|
7706 |
|
7707 |
return $string;
|
7708 |
}
|
7709 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7710 |
protected static $libFeatureExists = ['feature'];
|
7711 |
protected function libFeatureExists($args)
|
7712 |
{
|
7713 |
-
$string = $this->
|
7714 |
$name = $this->compileStringContent($string);
|
7715 |
|
7716 |
return $this->toBool(
|
@@ -7721,18 +9124,18 @@ class Compiler
|
|
7721 |
protected static $libFunctionExists = ['name'];
|
7722 |
protected function libFunctionExists($args)
|
7723 |
{
|
7724 |
-
$string = $this->
|
7725 |
$name = $this->compileStringContent($string);
|
7726 |
|
7727 |
// user defined functions
|
7728 |
if ($this->has(static::$namespaces['function'] . $name)) {
|
7729 |
-
return true;
|
7730 |
}
|
7731 |
|
7732 |
$name = $this->normalizeName($name);
|
7733 |
|
7734 |
if (isset($this->userFunctions[$name])) {
|
7735 |
-
return true;
|
7736 |
}
|
7737 |
|
7738 |
// built-in functions
|
@@ -7744,30 +9147,31 @@ class Compiler
|
|
7744 |
protected static $libGlobalVariableExists = ['name'];
|
7745 |
protected function libGlobalVariableExists($args)
|
7746 |
{
|
7747 |
-
$string = $this->
|
7748 |
$name = $this->compileStringContent($string);
|
7749 |
|
7750 |
-
return $this->has($name, $this->rootEnv);
|
7751 |
}
|
7752 |
|
7753 |
protected static $libMixinExists = ['name'];
|
7754 |
protected function libMixinExists($args)
|
7755 |
{
|
7756 |
-
$string = $this->
|
7757 |
$name = $this->compileStringContent($string);
|
7758 |
|
7759 |
-
return $this->has(static::$namespaces['mixin'] . $name);
|
7760 |
}
|
7761 |
|
7762 |
protected static $libVariableExists = ['name'];
|
7763 |
protected function libVariableExists($args)
|
7764 |
{
|
7765 |
-
$string = $this->
|
7766 |
$name = $this->compileStringContent($string);
|
7767 |
|
7768 |
-
return $this->has($name);
|
7769 |
}
|
7770 |
|
|
|
7771 |
/**
|
7772 |
* Workaround IE7's content counter bug.
|
7773 |
*
|
@@ -7777,7 +9181,7 @@ class Compiler
|
|
7777 |
*/
|
7778 |
protected function libCounter($args)
|
7779 |
{
|
7780 |
-
$list = array_map([$this, 'compileValue'], $args);
|
7781 |
|
7782 |
return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
|
7783 |
}
|
@@ -7785,24 +9189,21 @@ class Compiler
|
|
7785 |
protected static $libRandom = ['limit:null'];
|
7786 |
protected function libRandom($args)
|
7787 |
{
|
7788 |
-
if (isset($args[0])
|
7789 |
-
$n = $this->
|
7790 |
|
7791 |
if ($n < 1) {
|
7792 |
-
throw
|
7793 |
-
}
|
7794 |
-
|
7795 |
-
if (round($n - \intval($n), Node\Number::PRECISION) > 0) {
|
7796 |
-
throw $this->error("Expected \$limit to be an integer but got $n for `random`");
|
7797 |
}
|
7798 |
|
7799 |
-
return new
|
7800 |
}
|
7801 |
|
7802 |
$max = mt_getrandmax();
|
7803 |
-
return new
|
7804 |
}
|
7805 |
|
|
|
7806 |
protected function libUniqueId()
|
7807 |
{
|
7808 |
static $id;
|
@@ -7818,6 +9219,12 @@ class Compiler
|
|
7818 |
return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
|
7819 |
}
|
7820 |
|
|
|
|
|
|
|
|
|
|
|
|
|
7821 |
protected function inspectFormatValue($value, $force_enclosing_display = false)
|
7822 |
{
|
7823 |
if ($value === static::$null) {
|
@@ -7826,6 +9233,10 @@ class Compiler
|
|
7826 |
|
7827 |
$stringValue = [$value];
|
7828 |
|
|
|
|
|
|
|
|
|
7829 |
if ($value[0] === Type::T_LIST) {
|
7830 |
if (end($value[2]) === static::$null) {
|
7831 |
array_pop($value[2]);
|
@@ -7864,9 +9275,11 @@ class Compiler
|
|
7864 |
/**
|
7865 |
* Preprocess selector args
|
7866 |
*
|
7867 |
-
* @param array
|
|
|
|
|
7868 |
*
|
7869 |
-
* @return array
|
7870 |
*/
|
7871 |
protected function getSelectorArg($arg, $varname = null, $allowParent = false)
|
7872 |
{
|
@@ -7877,18 +9290,19 @@ class Compiler
|
|
7877 |
}
|
7878 |
|
7879 |
if (! $this->checkSelectorArgType($arg)) {
|
7880 |
-
$var_display = ($varname ? ' $' . $varname . ':' : '');
|
7881 |
$var_value = $this->compileValue($arg);
|
7882 |
-
throw
|
7883 |
-
. " a list of strings, or a list of lists of strings");
|
7884 |
}
|
7885 |
|
7886 |
-
|
|
|
|
|
|
|
7887 |
$arg = $this->compileValue($arg);
|
7888 |
|
7889 |
$parsedSelector = [];
|
7890 |
|
7891 |
-
if ($parser->parseSelector($arg, $parsedSelector)) {
|
7892 |
$selector = $this->evalSelectors($parsedSelector);
|
7893 |
$gluedSelector = $this->glueFunctionSelectors($selector);
|
7894 |
|
@@ -7896,8 +9310,7 @@ class Compiler
|
|
7896 |
foreach ($gluedSelector as $selector) {
|
7897 |
foreach ($selector as $s) {
|
7898 |
if (in_array(static::$selfSelector, $s)) {
|
7899 |
-
|
7900 |
-
throw $this->error("Error:{$var_display} Parent selectors aren't allowed here.");
|
7901 |
}
|
7902 |
}
|
7903 |
}
|
@@ -7906,8 +9319,7 @@ class Compiler
|
|
7906 |
return $gluedSelector;
|
7907 |
}
|
7908 |
|
7909 |
-
|
7910 |
-
throw $this->error("Error:{$var_display} expected more input, invalid selector.");
|
7911 |
}
|
7912 |
|
7913 |
/**
|
@@ -7937,11 +9349,11 @@ class Compiler
|
|
7937 |
*
|
7938 |
* @param array $selectors
|
7939 |
*
|
7940 |
-
* @return
|
7941 |
*/
|
7942 |
protected function formatOutputSelector($selectors)
|
7943 |
{
|
7944 |
-
$selectors = $this->
|
7945 |
|
7946 |
return $selectors;
|
7947 |
}
|
@@ -7954,7 +9366,7 @@ class Compiler
|
|
7954 |
$super = $this->getSelectorArg($super, 'super');
|
7955 |
$sub = $this->getSelectorArg($sub, 'sub');
|
7956 |
|
7957 |
-
return $this->isSuperSelector($super, $sub);
|
7958 |
}
|
7959 |
|
7960 |
/**
|
@@ -8197,6 +9609,10 @@ class Compiler
|
|
8197 |
$this->extendsMap = [];
|
8198 |
|
8199 |
foreach ($extendee as $es) {
|
|
|
|
|
|
|
|
|
8200 |
// only use the first one
|
8201 |
$this->pushExtends(reset($es), $extender, null);
|
8202 |
}
|
@@ -8297,7 +9713,7 @@ class Compiler
|
|
8297 |
* @param array $compound1
|
8298 |
* @param array $compound2
|
8299 |
*
|
8300 |
-
* @return array
|
8301 |
*/
|
8302 |
protected function unifyCompoundSelectors($compound1, $compound2)
|
8303 |
{
|
@@ -8413,7 +9829,7 @@ class Compiler
|
|
8413 |
* @param array $part
|
8414 |
* @param array $compound
|
8415 |
*
|
8416 |
-
* @return array|
|
8417 |
*/
|
8418 |
protected function matchPartInCompound($part, $compound)
|
8419 |
{
|
@@ -8508,7 +9924,7 @@ class Compiler
|
|
8508 |
* @param string $tag1
|
8509 |
* @param string $tag2
|
8510 |
*
|
8511 |
-
* @return array|
|
8512 |
*/
|
8513 |
protected function checkCompatibleTags($tag1, $tag2)
|
8514 |
{
|
@@ -8531,9 +9947,9 @@ class Compiler
|
|
8531 |
/**
|
8532 |
* Find the html tag name in a selector parts list
|
8533 |
*
|
8534 |
-
* @param
|
8535 |
*
|
8536 |
-
* @return
|
8537 |
*/
|
8538 |
protected function findTagName($parts)
|
8539 |
{
|
@@ -8570,7 +9986,11 @@ class Compiler
|
|
8570 |
protected static $libScssphpGlob = ['pattern'];
|
8571 |
protected function libScssphpGlob($args)
|
8572 |
{
|
8573 |
-
|
|
|
|
|
|
|
|
|
8574 |
$pattern = $this->compileStringContent($string);
|
8575 |
$matches = glob($pattern);
|
8576 |
$listParts = [];
|
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 string|\ScssPhp\ScssPhp\Formatter
|
198 |
*/
|
199 |
+
protected $formatter = Expanded::class;
|
200 |
|
201 |
+
/**
|
202 |
+
* @var Environment
|
203 |
+
*/
|
204 |
protected $rootEnv;
|
205 |
+
/**
|
206 |
+
* @var OutputBlock|null
|
207 |
+
*/
|
208 |
protected $rootBlock;
|
209 |
|
210 |
/**
|
211 |
* @var \ScssPhp\ScssPhp\Compiler\Environment
|
212 |
*/
|
213 |
protected $env;
|
214 |
+
/**
|
215 |
+
* @var OutputBlock|null
|
216 |
+
*/
|
217 |
protected $scope;
|
218 |
+
/**
|
219 |
+
* @var Environment|null
|
220 |
+
*/
|
221 |
protected $storeEnv;
|
222 |
+
/**
|
223 |
+
* @var bool|null
|
224 |
+
*/
|
225 |
protected $charsetSeen;
|
226 |
+
/**
|
227 |
+
* @var array<int, string|null>
|
228 |
+
*/
|
229 |
protected $sourceNames;
|
230 |
|
231 |
+
/**
|
232 |
+
* @var Cache|null
|
233 |
+
*/
|
234 |
protected $cache;
|
235 |
|
236 |
+
/**
|
237 |
+
* @var bool
|
238 |
+
*/
|
239 |
+
protected $cacheCheckImportResolutions = false;
|
240 |
+
|
241 |
+
/**
|
242 |
+
* @var int
|
243 |
+
*/
|
244 |
protected $indentLevel;
|
245 |
+
/**
|
246 |
+
* @var array[]
|
247 |
+
*/
|
248 |
protected $extends;
|
249 |
+
/**
|
250 |
+
* @var array<string, int[]>
|
251 |
+
*/
|
252 |
protected $extendsMap;
|
253 |
+
|
254 |
+
/**
|
255 |
+
* @var array<string, int>
|
256 |
+
*/
|
257 |
+
protected $parsedFiles = [];
|
258 |
+
|
259 |
+
/**
|
260 |
+
* @var Parser|null
|
261 |
+
*/
|
262 |
protected $parser;
|
263 |
+
/**
|
264 |
+
* @var int|null
|
265 |
+
*/
|
266 |
protected $sourceIndex;
|
267 |
+
/**
|
268 |
+
* @var int|null
|
269 |
+
*/
|
270 |
protected $sourceLine;
|
271 |
+
/**
|
272 |
+
* @var int|null
|
273 |
+
*/
|
274 |
protected $sourceColumn;
|
275 |
+
/**
|
276 |
+
* @var bool|null
|
277 |
+
*/
|
278 |
protected $shouldEvaluate;
|
279 |
+
/**
|
280 |
+
* @var null
|
281 |
+
* @deprecated
|
282 |
+
*/
|
283 |
protected $ignoreErrors;
|
284 |
+
/**
|
285 |
+
* @var bool
|
286 |
+
*/
|
287 |
protected $ignoreCallStackMessage = false;
|
288 |
|
289 |
+
/**
|
290 |
+
* @var array[]
|
291 |
+
*/
|
292 |
protected $callStack = [];
|
293 |
|
294 |
+
/**
|
295 |
+
* @var array
|
296 |
+
* @phpstan-var list<array{currentDir: string|null, path: string, filePath: string}>
|
297 |
+
*/
|
298 |
+
private $resolvedImports = [];
|
299 |
+
|
300 |
+
/**
|
301 |
+
* The directory of the currently processed file
|
302 |
+
*
|
303 |
+
* @var string|null
|
304 |
+
*/
|
305 |
+
private $currentDirectory;
|
306 |
+
|
307 |
+
/**
|
308 |
+
* The directory of the input file
|
309 |
+
*
|
310 |
+
* @var string
|
311 |
+
*/
|
312 |
+
private $rootDirectory;
|
313 |
+
|
314 |
+
/**
|
315 |
+
* @var bool
|
316 |
+
*/
|
317 |
+
private $legacyCwdImportPath = true;
|
318 |
+
|
319 |
+
/**
|
320 |
+
* @var LoggerInterface
|
321 |
+
*/
|
322 |
+
private $logger;
|
323 |
+
|
324 |
+
/**
|
325 |
+
* @var array<string, bool>
|
326 |
+
*/
|
327 |
+
private $warnedChildFunctions = [];
|
328 |
+
|
329 |
/**
|
330 |
* Constructor
|
331 |
*
|
332 |
* @param array|null $cacheOptions
|
333 |
+
* @phpstan-param array{cacheDir?: string, prefix?: string, forceRefresh?: string, checkImportResolutions?: bool}|null $cacheOptions
|
334 |
*/
|
335 |
public function __construct($cacheOptions = null)
|
336 |
{
|
|
|
337 |
$this->sourceNames = [];
|
338 |
|
339 |
if ($cacheOptions) {
|
340 |
$this->cache = new Cache($cacheOptions);
|
341 |
+
if (!empty($cacheOptions['checkImportResolutions'])) {
|
342 |
+
$this->cacheCheckImportResolutions = true;
|
343 |
+
}
|
344 |
}
|
345 |
|
346 |
+
$this->logger = new StreamLogger(fopen('php://stderr', 'w'), true);
|
347 |
}
|
348 |
|
349 |
/**
|
350 |
* Get compiler options
|
351 |
*
|
352 |
+
* @return array<string, mixed>
|
353 |
+
*
|
354 |
+
* @internal
|
355 |
*/
|
356 |
public function getCompileOptions()
|
357 |
{
|
363 |
'sourceMap' => serialize($this->sourceMap),
|
364 |
'sourceMapOptions' => $this->sourceMapOptions,
|
365 |
'formatter' => $this->formatter,
|
366 |
+
'legacyImportPath' => $this->legacyCwdImportPath,
|
367 |
];
|
368 |
|
369 |
return $options;
|
370 |
}
|
371 |
|
372 |
+
/**
|
373 |
+
* Sets an alternative logger.
|
374 |
+
*
|
375 |
+
* Changing the logger in the middle of the compilation is not
|
376 |
+
* supported and will result in an undefined behavior.
|
377 |
+
*
|
378 |
+
* @param LoggerInterface $logger
|
379 |
+
*
|
380 |
+
* @return void
|
381 |
+
*/
|
382 |
+
public function setLogger(LoggerInterface $logger)
|
383 |
+
{
|
384 |
+
$this->logger = $logger;
|
385 |
+
}
|
386 |
+
|
387 |
/**
|
388 |
* Set an alternative error output stream, for testing purpose only
|
389 |
*
|
390 |
* @param resource $handle
|
391 |
+
*
|
392 |
+
* @return void
|
393 |
+
*
|
394 |
+
* @deprecated Use {@see setLogger} instead
|
395 |
*/
|
396 |
public function setErrorOuput($handle)
|
397 |
{
|
398 |
+
@trigger_error('The method "setErrorOuput" is deprecated. Use "setLogger" instead.', E_USER_DEPRECATED);
|
399 |
+
|
400 |
+
$this->logger = new StreamLogger($handle);
|
401 |
}
|
402 |
|
403 |
/**
|
404 |
* Compile scss
|
405 |
*
|
406 |
+
* @param string $code
|
407 |
+
* @param string|null $path
|
|
|
|
|
408 |
*
|
409 |
* @return string
|
410 |
+
*
|
411 |
+
* @throws SassException when the source fails to compile
|
412 |
+
*
|
413 |
+
* @deprecated Use {@see compileString} instead.
|
414 |
*/
|
415 |
public function compile($code, $path = null)
|
416 |
{
|
417 |
+
@trigger_error(sprintf('The "%s" method is deprecated. Use "compileString" instead.', __METHOD__), E_USER_DEPRECATED);
|
|
|
|
|
|
|
418 |
|
419 |
+
$result = $this->compileString($code, $path);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
420 |
|
421 |
+
$sourceMap = $result->getSourceMap();
|
422 |
+
|
423 |
+
if ($sourceMap !== null) {
|
424 |
+
if ($this->sourceMap instanceof SourceMapGenerator) {
|
425 |
+
$this->sourceMap->saveMap($sourceMap);
|
426 |
+
} elseif ($this->sourceMap === self::SOURCE_MAP_FILE) {
|
427 |
+
$sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
|
428 |
+
$sourceMapGenerator->saveMap($sourceMap);
|
429 |
}
|
430 |
}
|
431 |
|
432 |
+
return $result->getCss();
|
433 |
+
}
|
434 |
+
|
435 |
+
/**
|
436 |
+
* Compile scss
|
437 |
+
*
|
438 |
+
* @param string $source
|
439 |
+
* @param string|null $path
|
440 |
+
*
|
441 |
+
* @return CompilationResult
|
442 |
+
*
|
443 |
+
* @throws SassException when the source fails to compile
|
444 |
+
*/
|
445 |
+
public function compileString($source, $path = null)
|
446 |
+
{
|
447 |
+
if ($this->cache) {
|
448 |
+
$cacheKey = ($path ? $path : '(stdin)') . ':' . md5($source);
|
449 |
+
$compileOptions = $this->getCompileOptions();
|
450 |
+
$cachedResult = $this->cache->getCache('compile', $cacheKey, $compileOptions);
|
451 |
+
|
452 |
+
if ($cachedResult instanceof CachedResult && $this->isFreshCachedResult($cachedResult)) {
|
453 |
+
return $cachedResult->getResult();
|
454 |
+
}
|
455 |
+
}
|
456 |
|
457 |
$this->indentLevel = -1;
|
458 |
$this->extends = [];
|
466 |
$this->charsetSeen = null;
|
467 |
$this->shouldEvaluate = null;
|
468 |
$this->ignoreCallStackMessage = false;
|
469 |
+
$this->parsedFiles = [];
|
470 |
+
$this->importedFiles = [];
|
471 |
+
$this->resolvedImports = [];
|
472 |
|
473 |
+
if (!\is_null($path) && is_file($path)) {
|
474 |
+
$path = realpath($path) ?: $path;
|
475 |
+
$this->currentDirectory = dirname($path);
|
476 |
+
$this->rootDirectory = $this->currentDirectory;
|
477 |
+
} else {
|
478 |
+
$this->currentDirectory = null;
|
479 |
+
$this->rootDirectory = getcwd();
|
480 |
+
}
|
481 |
|
482 |
+
try {
|
483 |
+
$this->parser = $this->parserFactory($path);
|
484 |
+
$tree = $this->parser->parse($source);
|
485 |
+
$this->parser = null;
|
486 |
|
487 |
+
$this->formatter = new $this->formatter();
|
488 |
+
$this->rootBlock = null;
|
489 |
+
$this->rootEnv = $this->pushEnv($tree);
|
490 |
|
491 |
+
$warnCallback = function ($message, $deprecation) {
|
492 |
+
$this->logger->warn($message, $deprecation);
|
493 |
+
};
|
494 |
+
$previousWarnCallback = Warn::setCallback($warnCallback);
|
495 |
|
496 |
+
try {
|
497 |
+
$this->injectVariables($this->registeredVars);
|
498 |
+
$this->compileRoot($tree);
|
499 |
+
$this->popEnv();
|
500 |
+
} finally {
|
501 |
+
Warn::setCallback($previousWarnCallback);
|
502 |
}
|
|
|
503 |
|
504 |
+
$sourceMapGenerator = null;
|
505 |
+
|
506 |
+
if ($this->sourceMap) {
|
507 |
+
if (\is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
|
508 |
+
$sourceMapGenerator = $this->sourceMap;
|
509 |
+
$this->sourceMap = self::SOURCE_MAP_FILE;
|
510 |
+
} elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
|
511 |
+
$sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
|
512 |
+
}
|
513 |
+
}
|
514 |
|
515 |
+
$out = $this->formatter->format($this->scope, $sourceMapGenerator);
|
|
|
|
|
516 |
|
517 |
+
$prefix = '';
|
|
|
|
|
|
|
518 |
|
519 |
+
if (!$this->charsetSeen) {
|
520 |
+
if (strlen($out) !== Util::mbStrlen($out)) {
|
521 |
+
$prefix = '@charset "UTF-8";' . "\n";
|
522 |
+
$out = $prefix . $out;
|
523 |
+
}
|
524 |
}
|
525 |
|
526 |
+
$sourceMap = null;
|
527 |
+
|
528 |
+
if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
|
529 |
+
$sourceMap = $sourceMapGenerator->generateJson($prefix);
|
530 |
+
$sourceMapUrl = null;
|
531 |
+
|
532 |
+
switch ($this->sourceMap) {
|
533 |
+
case self::SOURCE_MAP_INLINE:
|
534 |
+
$sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
|
535 |
+
break;
|
536 |
+
|
537 |
+
case self::SOURCE_MAP_FILE:
|
538 |
+
if (isset($this->sourceMapOptions['sourceMapURL'])) {
|
539 |
+
$sourceMapUrl = $this->sourceMapOptions['sourceMapURL'];
|
540 |
+
}
|
541 |
+
break;
|
542 |
+
}
|
543 |
+
|
544 |
+
if ($sourceMapUrl !== null) {
|
545 |
+
$out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
|
546 |
+
}
|
547 |
+
}
|
548 |
+
} catch (SassScriptException $e) {
|
549 |
+
throw new CompilerException($this->addLocationToMessage($e->getMessage()), 0, $e);
|
550 |
+
}
|
551 |
+
|
552 |
+
$includedFiles = [];
|
553 |
+
|
554 |
+
foreach ($this->resolvedImports as $resolvedImport) {
|
555 |
+
$includedFiles[$resolvedImport['filePath']] = $resolvedImport['filePath'];
|
556 |
}
|
557 |
|
558 |
+
$result = new CompilationResult($out, $sourceMap, array_values($includedFiles));
|
559 |
+
|
560 |
if ($this->cache && isset($cacheKey) && isset($compileOptions)) {
|
561 |
+
$this->cache->setCache('compile', $cacheKey, new CachedResult($result, $this->parsedFiles, $this->resolvedImports), $compileOptions);
|
562 |
+
}
|
|
|
|
|
563 |
|
564 |
+
// Reset state to free memory
|
565 |
+
// TODO in 2.0, reset parsedFiles as well when the getter is removed.
|
566 |
+
$this->resolvedImports = [];
|
567 |
+
$this->importedFiles = [];
|
568 |
+
|
569 |
+
return $result;
|
570 |
+
}
|
571 |
+
|
572 |
+
/**
|
573 |
+
* @param CachedResult $result
|
574 |
+
*
|
575 |
+
* @return bool
|
576 |
+
*/
|
577 |
+
private function isFreshCachedResult(CachedResult $result)
|
578 |
+
{
|
579 |
+
// check if any dependency file changed since the result was compiled
|
580 |
+
foreach ($result->getParsedFiles() as $file => $mtime) {
|
581 |
+
if (! is_file($file) || filemtime($file) !== $mtime) {
|
582 |
+
return false;
|
583 |
+
}
|
584 |
}
|
585 |
|
586 |
+
if ($this->cacheCheckImportResolutions) {
|
587 |
+
$resolvedImports = [];
|
588 |
+
|
589 |
+
foreach ($result->getResolvedImports() as $import) {
|
590 |
+
$currentDir = $import['currentDir'];
|
591 |
+
$path = $import['path'];
|
592 |
+
// store the check across all the results in memory to avoid multiple findImport() on the same path
|
593 |
+
// with same context.
|
594 |
+
// this is happening in a same hit with multiple compilations (especially with big frameworks)
|
595 |
+
if (empty($resolvedImports[$currentDir][$path])) {
|
596 |
+
$resolvedImports[$currentDir][$path] = $this->findImport($path, $currentDir);
|
597 |
+
}
|
598 |
+
|
599 |
+
if ($resolvedImports[$currentDir][$path] !== $import['filePath']) {
|
600 |
+
return false;
|
601 |
+
}
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
return true;
|
606 |
}
|
607 |
|
608 |
/**
|
609 |
* Instantiate parser
|
610 |
*
|
611 |
+
* @param string|null $path
|
612 |
*
|
613 |
* @return \ScssPhp\ScssPhp\Parser
|
614 |
*/
|
621 |
// Otherwise, the CSS will be rendered as-is. It can even be extended!
|
622 |
$cssOnly = false;
|
623 |
|
624 |
+
if ($path !== null && substr($path, -4) === '.css') {
|
625 |
$cssOnly = true;
|
626 |
}
|
627 |
|
628 |
+
$parser = new Parser($path, \count($this->sourceNames), $this->encoding, $this->cache, $cssOnly, $this->logger);
|
629 |
|
630 |
$this->sourceNames[] = $path;
|
631 |
$this->addParsedFile($path);
|
658 |
* @param array $target
|
659 |
* @param array $origin
|
660 |
* @param array|null $block
|
661 |
+
*
|
662 |
+
* @return void
|
663 |
*/
|
664 |
protected function pushExtends($target, $origin, $block)
|
665 |
{
|
678 |
/**
|
679 |
* Make output block
|
680 |
*
|
681 |
+
* @param string|null $type
|
682 |
+
* @param string[]|null $selectors
|
683 |
*
|
684 |
* @return \ScssPhp\ScssPhp\Formatter\OutputBlock
|
685 |
*/
|
710 |
* Compile root
|
711 |
*
|
712 |
* @param \ScssPhp\ScssPhp\Block $rootBlock
|
713 |
+
*
|
714 |
+
* @return void
|
715 |
*/
|
716 |
protected function compileRoot(Block $rootBlock)
|
717 |
{
|
724 |
|
725 |
/**
|
726 |
* Report missing selectors
|
727 |
+
*
|
728 |
+
* @return void
|
729 |
*/
|
730 |
protected function missingSelectors()
|
731 |
{
|
754 |
*
|
755 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
756 |
* @param string $parentKey
|
757 |
+
*
|
758 |
+
* @return void
|
759 |
*/
|
760 |
protected function flattenSelectors(OutputBlock $block, $parentKey = null)
|
761 |
{
|
811 |
}
|
812 |
|
813 |
/**
|
814 |
+
* Glue parts of :not( or :nth-child( ... that are in general split in selectors parts
|
815 |
*
|
816 |
* @param array $parts
|
817 |
*
|
852 |
* @param array $out
|
853 |
* @param integer $from
|
854 |
* @param boolean $initial
|
855 |
+
*
|
856 |
+
* @return void
|
857 |
*/
|
858 |
protected function matchExtends($selector, &$out, $from = 0, $initial = true)
|
859 |
{
|
1007 |
*
|
1008 |
* @param array $out
|
1009 |
* @param array $extended
|
1010 |
+
*
|
1011 |
+
* @return void
|
1012 |
*/
|
1013 |
protected function pushOrMergeExtentedSelector(&$out, $extended)
|
1014 |
{
|
1100 |
$buffer = $matches[2];
|
1101 |
$parser = $this->parserFactory(__METHOD__);
|
1102 |
|
1103 |
+
if ($parser->parseSelector($buffer, $subSelectors, false)) {
|
1104 |
foreach ($subSelectors as $ksub => $subSelector) {
|
1105 |
$subExtended = [];
|
1106 |
$this->matchExtends($subSelector, $subExtended, 0, false);
|
1260 |
* Compile media
|
1261 |
*
|
1262 |
* @param \ScssPhp\ScssPhp\Block $media
|
1263 |
+
*
|
1264 |
+
* @return void
|
1265 |
*/
|
1266 |
protected function compileMedia(Block $media)
|
1267 |
{
|
1269 |
|
1270 |
$mediaQueries = $this->compileMediaQuery($this->multiplyMedia($this->env));
|
1271 |
|
1272 |
+
if (! empty($mediaQueries)) {
|
1273 |
$previousScope = $this->scope;
|
1274 |
$parentScope = $this->mediaParent($this->scope);
|
1275 |
|
1309 |
$wrapped->children = $media->children;
|
1310 |
|
1311 |
$media->children = [[Type::T_BLOCK, $wrapped]];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1312 |
}
|
1313 |
|
1314 |
$this->compileChildrenNoReturn($media->children, $this->scope);
|
1342 |
/**
|
1343 |
* Compile directive
|
1344 |
*
|
1345 |
+
* @param \ScssPhp\ScssPhp\Block|array $directive
|
1346 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
1347 |
+
*
|
1348 |
+
* @return void
|
1349 |
*/
|
1350 |
protected function compileDirective($directive, OutputBlock $out)
|
1351 |
{
|
1387 |
* directive names can include some interpolation
|
1388 |
*
|
1389 |
* @param string|array $directiveName
|
1390 |
+
* @return string
|
1391 |
* @throws CompilerException
|
1392 |
*/
|
1393 |
protected function compileDirectiveName($directiveName)
|
1403 |
* Compile at-root
|
1404 |
*
|
1405 |
* @param \ScssPhp\ScssPhp\Block $block
|
1406 |
+
*
|
1407 |
+
* @return void
|
1408 |
*/
|
1409 |
protected function compileAtRoot(Block $block)
|
1410 |
{
|
1430 |
}
|
1431 |
|
1432 |
$selfParent = $block->selfParent;
|
1433 |
+
assert($selfParent !== null, 'at-root blocks must have a selfParent set.');
|
1434 |
|
1435 |
if (
|
1436 |
+
! $selfParent->selectors &&
|
1437 |
isset($block->parent) && $block->parent &&
|
1438 |
isset($block->parent->selectors) && $block->parent->selectors
|
1439 |
) {
|
1462 |
* @param array $with
|
1463 |
* @param array $without
|
1464 |
*
|
1465 |
+
* @return OutputBlock
|
1466 |
*/
|
1467 |
protected function filterScopeWithWithout($scope, $with, $without)
|
1468 |
{
|
1469 |
$filteredScopes = [];
|
1470 |
$childStash = [];
|
1471 |
|
1472 |
+
if ($scope->type === Type::T_ROOT) {
|
1473 |
return $scope;
|
1474 |
}
|
1475 |
|
1476 |
// start from the root
|
1477 |
+
while ($scope->parent && $scope->parent->type !== Type::T_ROOT) {
|
1478 |
array_unshift($childStash, $scope);
|
1479 |
$scope = $scope->parent;
|
1480 |
}
|
1535 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $scope
|
1536 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $previousScope
|
1537 |
*
|
1538 |
+
* @return OutputBlock
|
1539 |
*/
|
1540 |
protected function completeScope($scope, $previousScope)
|
1541 |
{
|
1602 |
}
|
1603 |
}
|
1604 |
|
1605 |
+
if ($this->mapHasKey($withCondition, static::$with)) {
|
1606 |
$without = []; // cancel the default
|
1607 |
$list = $this->coerceList($this->libMapGet([$withCondition, static::$with]));
|
1608 |
|
1613 |
}
|
1614 |
}
|
1615 |
|
1616 |
+
if ($this->mapHasKey($withCondition, static::$without)) {
|
1617 |
$without = []; // cancel the default
|
1618 |
$list = $this->coerceList($this->libMapGet([$withCondition, static::$without]));
|
1619 |
|
1631 |
/**
|
1632 |
* Filter env stack
|
1633 |
*
|
1634 |
+
* @param Environment[] $envs
|
1635 |
* @param array $with
|
1636 |
* @param array $without
|
1637 |
*
|
1638 |
+
* @return Environment
|
1639 |
+
*
|
1640 |
+
* @phpstan-param non-empty-array<Environment> $envs
|
1641 |
*/
|
1642 |
protected function filterWithWithout($envs, $with, $without)
|
1643 |
{
|
1692 |
$s = reset($s);
|
1693 |
}
|
1694 |
|
1695 |
+
if (\is_object($s) && $s instanceof Number) {
|
1696 |
return $this->testWithWithout('keyframes', $with, $without);
|
1697 |
}
|
1698 |
}
|
1729 |
* Compile keyframe block
|
1730 |
*
|
1731 |
* @param \ScssPhp\ScssPhp\Block $block
|
1732 |
+
* @param string[] $selectors
|
1733 |
+
*
|
1734 |
+
* @return void
|
1735 |
*/
|
1736 |
protected function compileKeyframeBlock(Block $block, $selectors)
|
1737 |
{
|
1760 |
*
|
1761 |
* @param \ScssPhp\ScssPhp\Block $block
|
1762 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
1763 |
+
*
|
1764 |
+
* @return void
|
1765 |
*/
|
1766 |
protected function compileNestedPropertiesBlock(Block $block, OutputBlock $out)
|
1767 |
{
|
1795 |
* Compile nested block
|
1796 |
*
|
1797 |
* @param \ScssPhp\ScssPhp\Block $block
|
1798 |
+
* @param string[] $selectors
|
1799 |
+
*
|
1800 |
+
* @return void
|
1801 |
*/
|
1802 |
protected function compileNestedBlock(Block $block, $selectors)
|
1803 |
{
|
1859 |
* @see Compiler::compileChild()
|
1860 |
*
|
1861 |
* @param \ScssPhp\ScssPhp\Block $block
|
1862 |
+
*
|
1863 |
+
* @return void
|
1864 |
*/
|
1865 |
protected function compileBlock(Block $block)
|
1866 |
{
|
1869 |
|
1870 |
$out = $this->makeOutputBlock(null);
|
1871 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1872 |
$this->scope->children[] = $out;
|
1873 |
|
1874 |
if (\count($block->children)) {
|
1900 |
* @param array $value
|
1901 |
* @param boolean $pushEnv
|
1902 |
*
|
1903 |
+
* @return string
|
1904 |
*/
|
1905 |
protected function compileCommentValue($value, $pushEnv = false)
|
1906 |
{
|
1911 |
$this->pushEnv();
|
1912 |
}
|
1913 |
|
|
|
|
|
|
|
1914 |
try {
|
1915 |
$c = $this->compileValue($value[2]);
|
1916 |
+
} catch (SassScriptException $e) {
|
1917 |
+
$this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $this->addLocationToMessage($e->getMessage()), true);
|
1918 |
+
// ignore error in comment compilation which are only interpolation
|
1919 |
+
} catch (SassException $e) {
|
1920 |
+
$this->logger->warn('Ignoring interpolation errors in multiline comments is deprecated and will be removed in ScssPhp 2.0. ' . $e->getMessage(), true);
|
1921 |
// ignore error in comment compilation which are only interpolation
|
1922 |
}
|
1923 |
|
|
|
|
|
1924 |
if ($pushEnv) {
|
1925 |
$this->popEnv();
|
1926 |
}
|
1933 |
* Compile root level comment
|
1934 |
*
|
1935 |
* @param array $block
|
1936 |
+
*
|
1937 |
+
* @return void
|
1938 |
*/
|
1939 |
protected function compileComment($block)
|
1940 |
{
|
1963 |
$buffer = $this->collapseSelectors($selectors);
|
1964 |
$parser = $this->parserFactory(__METHOD__);
|
1965 |
|
1966 |
+
try {
|
1967 |
+
$isValid = $parser->parseSelector($buffer, $newSelectors, true);
|
1968 |
+
} catch (ParserException $e) {
|
1969 |
+
throw $this->error($e->getMessage());
|
1970 |
+
}
|
1971 |
+
|
1972 |
+
if ($isValid) {
|
1973 |
$selectors = array_map([$this, 'evalSelector'], $newSelectors);
|
1974 |
}
|
1975 |
}
|
2002 |
if (\is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
|
2003 |
$p = $this->compileValue($p);
|
2004 |
|
2005 |
+
// force re-evaluation if self char or non standard char
|
2006 |
+
if (preg_match(',[^\w-],', $p)) {
|
2007 |
$this->shouldEvaluate = true;
|
2008 |
}
|
2009 |
} elseif (
|
2021 |
/**
|
2022 |
* Collapse selectors
|
2023 |
*
|
2024 |
+
* @param array $selectors
|
|
|
|
|
|
|
2025 |
*
|
2026 |
* @return string
|
2027 |
*/
|
2028 |
+
protected function collapseSelectors($selectors)
|
2029 |
{
|
2030 |
$parts = [];
|
2031 |
|
2032 |
foreach ($selectors as $selector) {
|
2033 |
$output = [];
|
|
|
2034 |
|
2035 |
foreach ($selector as $node) {
|
2036 |
$compound = '';
|
2042 |
}
|
2043 |
);
|
2044 |
|
2045 |
+
$output[] = $compound;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2046 |
}
|
2047 |
|
2048 |
+
$parts[] = implode(' ', $output);
|
2049 |
+
}
|
|
|
|
|
2050 |
|
2051 |
+
return implode(', ', $parts);
|
2052 |
+
}
|
2053 |
+
|
2054 |
+
/**
|
2055 |
+
* Collapse selectors
|
2056 |
+
*
|
2057 |
+
* @param array $selectors
|
2058 |
+
*
|
2059 |
+
* @return array
|
2060 |
+
*/
|
2061 |
+
private function collapseSelectorsAsList($selectors)
|
2062 |
+
{
|
2063 |
+
$parts = [];
|
2064 |
+
|
2065 |
+
foreach ($selectors as $selector) {
|
2066 |
+
$output = [];
|
2067 |
+
$glueNext = false;
|
2068 |
+
|
2069 |
+
foreach ($selector as $node) {
|
2070 |
+
$compound = '';
|
2071 |
+
|
2072 |
+
array_walk_recursive(
|
2073 |
+
$node,
|
2074 |
+
function ($value, $key) use (&$compound) {
|
2075 |
+
$compound .= $value;
|
2076 |
+
}
|
2077 |
+
);
|
2078 |
+
|
2079 |
+
if ($this->isImmediateRelationshipCombinator($compound)) {
|
2080 |
+
if (\count($output)) {
|
2081 |
+
$output[\count($output) - 1] .= ' ' . $compound;
|
2082 |
+
} else {
|
2083 |
+
$output[] = $compound;
|
2084 |
+
}
|
2085 |
+
|
2086 |
+
$glueNext = true;
|
2087 |
+
} elseif ($glueNext) {
|
2088 |
+
$output[\count($output) - 1] .= ' ' . $compound;
|
2089 |
+
$glueNext = false;
|
2090 |
+
} else {
|
2091 |
+
$output[] = $compound;
|
2092 |
+
}
|
2093 |
}
|
2094 |
|
2095 |
+
foreach ($output as &$o) {
|
2096 |
+
$o = [Type::T_STRING, '', [$o]];
|
2097 |
+
}
|
2098 |
|
2099 |
+
$parts[] = [Type::T_LIST, ' ', $output];
|
|
|
|
|
|
|
2100 |
}
|
2101 |
|
2102 |
+
return [Type::T_LIST, ',', $parts];
|
2103 |
}
|
2104 |
|
2105 |
/**
|
2106 |
* Parse down the selector and revert [self] to "&" before a reparsing
|
2107 |
*
|
2108 |
+
* @param array $selectors
|
2109 |
+
* @param string|null $replace
|
2110 |
*
|
2111 |
* @return array
|
2112 |
*/
|
2234 |
return false;
|
2235 |
}
|
2236 |
|
2237 |
+
/**
|
2238 |
+
* @param string $name
|
2239 |
+
*
|
2240 |
+
* @return void
|
2241 |
+
*/
|
2242 |
protected function pushCallStack($name = '')
|
2243 |
{
|
2244 |
$this->callStack[] = [
|
2258 |
}
|
2259 |
}
|
2260 |
|
2261 |
+
/**
|
2262 |
+
* @return void
|
2263 |
+
*/
|
2264 |
protected function popCallStack()
|
2265 |
{
|
2266 |
array_pop($this->callStack);
|
2273 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2274 |
* @param string $traceName
|
2275 |
*
|
2276 |
+
* @return array|Number|null
|
2277 |
*/
|
2278 |
protected function compileChildren($stms, OutputBlock $out, $traceName = '')
|
2279 |
{
|
2295 |
}
|
2296 |
|
2297 |
/**
|
2298 |
+
* Compile children and throw exception if unexpected `@return`
|
2299 |
*
|
2300 |
* @param array $stms
|
2301 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2302 |
* @param \ScssPhp\ScssPhp\Block $selfParent
|
2303 |
* @param string $traceName
|
2304 |
*
|
2305 |
+
* @return void
|
2306 |
+
*
|
2307 |
* @throws \Exception
|
2308 |
*/
|
2309 |
protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '')
|
2315 |
$stm[1]->selfParent = $selfParent;
|
2316 |
$ret = $this->compileChild($stm, $out);
|
2317 |
$stm[1]->selfParent = null;
|
2318 |
+
} elseif ($selfParent && \in_array($stm[0], [Type::T_INCLUDE, Type::T_EXTEND])) {
|
2319 |
$stm['selfParent'] = $selfParent;
|
2320 |
$ret = $this->compileChild($stm, $out);
|
2321 |
unset($stm['selfParent']);
|
2333 |
|
2334 |
|
2335 |
/**
|
2336 |
+
* evaluate media query : compile internal value keeping the structure unchanged
|
2337 |
*
|
2338 |
* @param array $queryList
|
2339 |
*
|
2403 |
*
|
2404 |
* @param array $queryList
|
2405 |
*
|
2406 |
+
* @return string[]
|
2407 |
*/
|
2408 |
protected function compileMediaQuery($queryList)
|
2409 |
{
|
2641 |
if ($rawPath[0] === Type::T_STRING) {
|
2642 |
$path = $this->compileStringContent($rawPath);
|
2643 |
|
2644 |
+
if (strpos($path, 'url(') !== 0 && $filePath = $this->findImport($path, $this->currentDirectory)) {
|
2645 |
+
$this->registerImport($this->currentDirectory, $path, $filePath);
|
2646 |
+
|
2647 |
+
if (! $once || ! \in_array($filePath, $this->importedFiles)) {
|
2648 |
+
$this->importFile($filePath, $out);
|
2649 |
+
$this->importedFiles[] = $filePath;
|
2650 |
}
|
2651 |
|
2652 |
return true;
|
2684 |
}
|
2685 |
|
2686 |
/**
|
2687 |
+
* @param array $rawPath
|
2688 |
* @return string
|
2689 |
* @throws CompilerException
|
2690 |
*/
|
2692 |
{
|
2693 |
$path = $this->compileValue($rawPath);
|
2694 |
|
2695 |
+
// case url() without quotes : suppress \r \n remaining in the path
|
2696 |
// if this is a real string there can not be CR or LF char
|
2697 |
if (strpos($path, 'url(') === 0) {
|
2698 |
$path = str_replace(array("\r", "\n"), array('', ' '), $path);
|
2699 |
} else {
|
2700 |
+
// if this is a file name in a string, spaces should be escaped
|
2701 |
$path = $this->reduce($rawPath);
|
2702 |
$path = $this->escapeImportPathString($path);
|
2703 |
$path = $this->compileValue($path);
|
2735 |
* Append a root directive like @import or @charset as near as the possible from the source code
|
2736 |
* (keeping before comments, @import and @charset coming before in the source code)
|
2737 |
*
|
2738 |
+
* @param string $line
|
2739 |
+
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2740 |
+
* @param array $allowed
|
2741 |
+
*
|
2742 |
+
* @return void
|
2743 |
*/
|
2744 |
protected function appendRootDirective($line, $out, $allowed = [Type::T_COMMENT])
|
2745 |
{
|
2787 |
*
|
2788 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2789 |
* @param string $type
|
2790 |
+
* @param string $line
|
2791 |
+
*
|
2792 |
+
* @return void
|
2793 |
*/
|
2794 |
protected function appendOutputLine(OutputBlock $out, $type, $line)
|
2795 |
{
|
2824 |
* @param array $child
|
2825 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
2826 |
*
|
2827 |
+
* @return array|Number|null
|
2828 |
*/
|
2829 |
protected function compileChild($child, OutputBlock $out)
|
2830 |
{
|
2838 |
$this->sourceColumn = $child[1]->sourceColumn;
|
2839 |
} elseif (! empty($out->sourceLine) && ! empty($out->sourceName)) {
|
2840 |
$this->sourceLine = $out->sourceLine;
|
2841 |
+
$sourceIndex = array_search($out->sourceName, $this->sourceNames);
|
2842 |
$this->sourceColumn = $out->sourceColumn;
|
2843 |
|
2844 |
+
if ($sourceIndex === false) {
|
2845 |
+
$sourceIndex = null;
|
2846 |
}
|
2847 |
+
$this->sourceIndex = $sourceIndex;
|
2848 |
}
|
2849 |
|
2850 |
switch ($child[0]) {
|
2969 |
$divider = $this->reduce($divider, true);
|
2970 |
}
|
2971 |
|
2972 |
+
if ($divider instanceof Number && \intval($divider->getDimension()) && $divider->unitless()) {
|
2973 |
$revert = false;
|
2974 |
}
|
2975 |
}
|
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'])) {
|
3115 |
$ret = $this->compileChildren($each->children, $out);
|
3116 |
|
3117 |
if ($ret) {
|
3118 |
+
$store = $this->env->store;
|
3119 |
+
$this->popEnv();
|
3120 |
+
$this->backPropagateEnv($store, $each->vars);
|
|
|
|
|
|
|
|
|
3121 |
|
3122 |
+
return $ret;
|
|
|
|
|
3123 |
}
|
3124 |
}
|
3125 |
$store = $this->env->store;
|
3135 |
$ret = $this->compileChildren($while->children, $out);
|
3136 |
|
3137 |
if ($ret) {
|
3138 |
+
return $ret;
|
|
|
|
|
|
|
|
|
|
|
|
|
3139 |
}
|
3140 |
}
|
3141 |
break;
|
3143 |
case Type::T_FOR:
|
3144 |
list(, $for) = $child;
|
3145 |
|
3146 |
+
$startNumber = $this->assertNumber($this->reduce($for->start, true));
|
3147 |
+
$endNumber = $this->assertNumber($this->reduce($for->end, true));
|
|
|
|
|
|
|
|
|
3148 |
|
3149 |
+
$start = $this->assertInteger($startNumber);
|
|
|
|
|
3150 |
|
3151 |
+
$numeratorUnits = $startNumber->getNumeratorUnits();
|
3152 |
+
$denominatorUnits = $startNumber->getDenominatorUnits();
|
|
|
3153 |
|
3154 |
+
$end = $this->assertInteger($endNumber->coerce($numeratorUnits, $denominatorUnits));
|
|
|
|
|
3155 |
|
3156 |
$d = $start < $end ? 1 : -1;
|
3157 |
|
3165 |
break;
|
3166 |
}
|
3167 |
|
3168 |
+
$this->set($for->var, new Number($start, $numeratorUnits, $denominatorUnits));
|
3169 |
$start += $d;
|
3170 |
|
3171 |
$ret = $this->compileChildren($for->children, $out);
|
3172 |
|
3173 |
if ($ret) {
|
3174 |
+
$store = $this->env->store;
|
3175 |
+
$this->popEnv();
|
3176 |
+
$this->backPropagateEnv($store, [$for->var]);
|
|
|
|
|
|
|
|
|
3177 |
|
3178 |
+
return $ret;
|
|
|
|
|
3179 |
}
|
3180 |
}
|
3181 |
|
3185 |
|
3186 |
break;
|
3187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
3188 |
case Type::T_RETURN:
|
3189 |
return $this->reduce($child[1], true);
|
3190 |
|
3299 |
case Type::T_DEBUG:
|
3300 |
list(, $value) = $child;
|
3301 |
|
3302 |
+
$fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
|
3303 |
$line = $this->sourceLine;
|
3304 |
$value = $this->compileDebugValue($value);
|
3305 |
|
3306 |
+
$this->logger->debug("$fname:$line DEBUG: $value");
|
3307 |
break;
|
3308 |
|
3309 |
case Type::T_WARN:
|
3310 |
list(, $value) = $child;
|
3311 |
|
3312 |
+
$fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
|
3313 |
$line = $this->sourceLine;
|
3314 |
$value = $this->compileDebugValue($value);
|
3315 |
|
3316 |
+
$this->logger->warn("$value\n on line $line of $fname");
|
3317 |
break;
|
3318 |
|
3319 |
case Type::T_ERROR:
|
3320 |
list(, $value) = $child;
|
3321 |
|
3322 |
+
$fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
|
3323 |
$line = $this->sourceLine;
|
3324 |
$value = $this->compileValue($this->reduce($value, true));
|
3325 |
|
3326 |
throw $this->error("File $fname on line $line ERROR: $value\n");
|
3327 |
|
|
|
|
|
|
|
3328 |
default:
|
3329 |
throw $this->error("unknown child type: $child[0]");
|
3330 |
}
|
3334 |
* Reduce expression to string
|
3335 |
*
|
3336 |
* @param array $exp
|
3337 |
+
* @param bool $keepParens
|
3338 |
*
|
3339 |
* @return array
|
3340 |
*/
|
3372 |
/**
|
3373 |
* Is truthy?
|
3374 |
*
|
3375 |
+
* @param array|Number $value
|
3376 |
*
|
3377 |
* @return boolean
|
3378 |
*/
|
3379 |
+
public function isTruthy($value)
|
3380 |
{
|
3381 |
return $value !== static::$false && $value !== static::$null;
|
3382 |
}
|
3420 |
/**
|
3421 |
* Reduce value
|
3422 |
*
|
3423 |
+
* @param array|Number $value
|
3424 |
* @param boolean $inExp
|
3425 |
*
|
3426 |
+
* @return array|Number
|
3427 |
*/
|
3428 |
protected function reduce($value, $inExp = false)
|
3429 |
{
|
3430 |
+
if ($value instanceof Number) {
|
3431 |
+
return $value;
|
3432 |
}
|
3433 |
|
3434 |
switch ($value[0]) {
|
3446 |
|
3447 |
// special case: looks like css shorthand
|
3448 |
if (
|
3449 |
+
$opName == 'div' && ! $inParens && ! $inExp &&
|
3450 |
+
(($right[0] !== Type::T_NUMBER && isset($right[2]) && $right[2] != '') ||
|
3451 |
($right[0] === Type::T_NUMBER && ! $right->unitless()))
|
3452 |
) {
|
3453 |
return $this->expToString($value);
|
3477 |
\is_callable([$this, $fn]) &&
|
3478 |
$genOp = true)
|
3479 |
) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3480 |
$shouldEval = $inParens || $inExp;
|
3481 |
|
3482 |
if (isset($passOp)) {
|
3486 |
}
|
3487 |
|
3488 |
if (isset($out)) {
|
|
|
|
|
|
|
|
|
3489 |
return $out;
|
3490 |
}
|
3491 |
}
|
3498 |
$inExp = $inExp || $this->shouldEval($exp);
|
3499 |
$exp = $this->reduce($exp);
|
3500 |
|
3501 |
+
if ($exp instanceof Number) {
|
3502 |
switch ($op) {
|
3503 |
case '+':
|
3504 |
+
return $exp;
|
3505 |
|
3506 |
case '-':
|
3507 |
+
return $exp->unaryMinus();
|
3508 |
}
|
3509 |
}
|
3510 |
|
3529 |
foreach ($value[2] as &$item) {
|
3530 |
$item = $this->reduce($item);
|
3531 |
}
|
3532 |
+
unset($item);
|
3533 |
+
|
3534 |
+
if (isset($value[3]) && \is_array($value[3])) {
|
3535 |
+
foreach ($value[3] as &$item) {
|
3536 |
+
$item = $this->reduce($item);
|
3537 |
+
}
|
3538 |
+
unset($item);
|
3539 |
+
}
|
3540 |
|
3541 |
return $value;
|
3542 |
|
3553 |
|
3554 |
case Type::T_STRING:
|
3555 |
foreach ($value[2] as &$item) {
|
3556 |
+
if (\is_array($item) || $item instanceof Number) {
|
3557 |
$item = $this->reduce($item);
|
3558 |
}
|
3559 |
}
|
3564 |
$value[1] = $this->reduce($value[1]);
|
3565 |
|
3566 |
if ($inExp) {
|
3567 |
+
return [Type::T_KEYWORD, $this->compileValue($value, false)];
|
3568 |
}
|
3569 |
|
3570 |
return $value;
|
3575 |
case Type::T_SELF:
|
3576 |
$selfParent = ! empty($this->env->block->selfParent) ? $this->env->block->selfParent : null;
|
3577 |
$selfSelector = $this->multiplySelectors($this->env, $selfParent);
|
3578 |
+
$selfSelector = $this->collapseSelectorsAsList($selfSelector);
|
3579 |
|
3580 |
return $selfSelector;
|
3581 |
|
3587 |
/**
|
3588 |
* Function caller
|
3589 |
*
|
3590 |
+
* @param string|array $functionReference
|
3591 |
+
* @param array $argValues
|
3592 |
*
|
3593 |
+
* @return array|Number
|
3594 |
*/
|
3595 |
protected function fncall($functionReference, $argValues)
|
3596 |
{
|
3635 |
|
3636 |
// special cases of css valid functions min/max
|
3637 |
$name = strtolower($name);
|
3638 |
+
if (\in_array($name, ['min', 'max']) && count($argValues) >= 1) {
|
3639 |
$cssFunction = $this->cssValidArg(
|
3640 |
[Type::T_FUNCTION_CALL, $name, $argValues],
|
3641 |
['min', 'max', 'calc', 'env', 'var']
|
3657 |
}
|
3658 |
}
|
3659 |
|
3660 |
+
/**
|
3661 |
+
* @param array|Number $arg
|
3662 |
+
* @param string[] $allowed_function
|
3663 |
+
* @param bool $inFunction
|
3664 |
+
*
|
3665 |
+
* @return array|Number|false
|
3666 |
+
*/
|
3667 |
protected function cssValidArg($arg, $allowed_function = [], $inFunction = false)
|
3668 |
{
|
3669 |
+
if ($arg instanceof Number) {
|
3670 |
+
return $this->stringifyFncallArgs($arg);
|
3671 |
+
}
|
3672 |
+
|
3673 |
switch ($arg[0]) {
|
3674 |
case Type::T_INTERPOLATE:
|
3675 |
return [Type::T_KEYWORD, $this->CompileValue($arg)];
|
3714 |
}
|
3715 |
return $this->stringifyFncallArgs($arg);
|
3716 |
|
|
|
|
|
|
|
3717 |
case Type::T_LIST:
|
3718 |
if (!$inFunction) {
|
3719 |
return false;
|
3751 |
|
3752 |
/**
|
3753 |
* Reformat fncall arguments to proper css function output
|
3754 |
+
*
|
3755 |
+
* @param array|Number $arg
|
3756 |
+
*
|
3757 |
+
* @return array|Number
|
3758 |
*/
|
3759 |
protected function stringifyFncallArgs($arg)
|
3760 |
{
|
3761 |
+
if ($arg instanceof Number) {
|
3762 |
+
return $arg;
|
3763 |
+
}
|
3764 |
|
3765 |
switch ($arg[0]) {
|
3766 |
case Type::T_LIST:
|
3825 |
$libName = $f[1];
|
3826 |
$prototype = isset(static::$$libName) ? static::$$libName : null;
|
3827 |
|
3828 |
+
if (\get_class($this) !== __CLASS__ && !isset($this->warnedChildFunctions[$libName])) {
|
3829 |
+
$r = new \ReflectionMethod($this, $libName);
|
3830 |
+
$declaringClass = $r->getDeclaringClass()->name;
|
3831 |
+
|
3832 |
+
$needsWarning = $this->warnedChildFunctions[$libName] = $declaringClass !== __CLASS__;
|
3833 |
+
|
3834 |
+
if ($needsWarning) {
|
3835 |
+
if (method_exists(__CLASS__, $libName)) {
|
3836 |
+
@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);
|
3837 |
+
} else {
|
3838 |
+
@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);
|
3839 |
+
}
|
3840 |
+
}
|
3841 |
+
}
|
3842 |
+
|
3843 |
return [Type::T_FUNCTION_REFERENCE, 'native', $name, $f, $prototype];
|
3844 |
}
|
3845 |
|
3862 |
/**
|
3863 |
* Normalize value
|
3864 |
*
|
3865 |
+
* @internal
|
3866 |
*
|
3867 |
+
* @param array|Number $value
|
3868 |
+
*
|
3869 |
+
* @return array|Number
|
3870 |
*/
|
3871 |
public function normalizeValue($value)
|
3872 |
{
|
3873 |
$value = $this->coerceForExpression($this->reduce($value));
|
3874 |
|
3875 |
+
if ($value instanceof Number) {
|
3876 |
+
return $value;
|
3877 |
+
}
|
3878 |
+
|
3879 |
switch ($value[0]) {
|
3880 |
case Type::T_LIST:
|
3881 |
$value = $this->extractInterpolation($value);
|
3897 |
case Type::T_STRING:
|
3898 |
return [$value[0], '"', [$this->compileStringContent($value)]];
|
3899 |
|
|
|
|
|
|
|
3900 |
case Type::T_INTERPOLATE:
|
3901 |
return [Type::T_KEYWORD, $this->compileValue($value)];
|
3902 |
|
3908 |
/**
|
3909 |
* Add numbers
|
3910 |
*
|
3911 |
+
* @param Number $left
|
3912 |
+
* @param Number $right
|
3913 |
*
|
3914 |
+
* @return Number
|
3915 |
*/
|
3916 |
+
protected function opAddNumberNumber(Number $left, Number $right)
|
3917 |
{
|
3918 |
+
return $left->plus($right);
|
3919 |
}
|
3920 |
|
3921 |
/**
|
3922 |
* Multiply numbers
|
3923 |
*
|
3924 |
+
* @param Number $left
|
3925 |
+
* @param Number $right
|
3926 |
*
|
3927 |
+
* @return Number
|
3928 |
*/
|
3929 |
+
protected function opMulNumberNumber(Number $left, Number $right)
|
3930 |
{
|
3931 |
+
return $left->times($right);
|
3932 |
}
|
3933 |
|
3934 |
/**
|
3935 |
* Subtract numbers
|
3936 |
*
|
3937 |
+
* @param Number $left
|
3938 |
+
* @param Number $right
|
3939 |
*
|
3940 |
+
* @return Number
|
3941 |
*/
|
3942 |
+
protected function opSubNumberNumber(Number $left, Number $right)
|
3943 |
{
|
3944 |
+
return $left->minus($right);
|
3945 |
}
|
3946 |
|
3947 |
/**
|
3948 |
* Divide numbers
|
3949 |
*
|
3950 |
+
* @param Number $left
|
3951 |
+
* @param Number $right
|
3952 |
*
|
3953 |
+
* @return Number
|
3954 |
*/
|
3955 |
+
protected function opDivNumberNumber(Number $left, Number $right)
|
3956 |
{
|
3957 |
+
return $left->dividedBy($right);
|
|
|
|
|
|
|
|
|
3958 |
}
|
3959 |
|
3960 |
/**
|
3961 |
* Mod numbers
|
3962 |
*
|
3963 |
+
* @param Number $left
|
3964 |
+
* @param Number $right
|
3965 |
*
|
3966 |
+
* @return Number
|
3967 |
*/
|
3968 |
+
protected function opModNumberNumber(Number $left, Number $right)
|
3969 |
{
|
3970 |
+
return $left->modulo($right);
|
|
|
|
|
|
|
|
|
3971 |
}
|
3972 |
|
3973 |
/**
|
4006 |
/**
|
4007 |
* Boolean and
|
4008 |
*
|
4009 |
+
* @param array|Number $left
|
4010 |
+
* @param array|Number $right
|
4011 |
* @param boolean $shouldEval
|
4012 |
*
|
4013 |
+
* @return array|Number|null
|
4014 |
*/
|
4015 |
protected function opAnd($left, $right, $shouldEval)
|
4016 |
{
|
4034 |
/**
|
4035 |
* Boolean or
|
4036 |
*
|
4037 |
+
* @param array|Number $left
|
4038 |
+
* @param array|Number $right
|
4039 |
* @param boolean $shouldEval
|
4040 |
*
|
4041 |
+
* @return array|Number|null
|
4042 |
*/
|
4043 |
protected function opOr($left, $right, $shouldEval)
|
4044 |
{
|
4070 |
*/
|
4071 |
protected function opColorColor($op, $left, $right)
|
4072 |
{
|
4073 |
+
if ($op !== '==' && $op !== '!=') {
|
4074 |
+
$warning = "Color arithmetic is deprecated and will be an error in future versions.\n"
|
4075 |
+
. "Consider using Sass's color functions instead.";
|
4076 |
+
$fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
|
4077 |
+
$line = $this->sourceLine;
|
4078 |
+
|
4079 |
+
Warn::deprecation("$warning\n on line $line of $fname");
|
4080 |
+
}
|
4081 |
+
|
4082 |
$out = [Type::T_COLOR];
|
4083 |
|
4084 |
foreach ([1, 2, 3] as $i) {
|
4139 |
*
|
4140 |
* @param string $op
|
4141 |
* @param array $left
|
4142 |
+
* @param Number $right
|
4143 |
*
|
4144 |
* @return array
|
4145 |
*/
|
4146 |
+
protected function opColorNumber($op, $left, Number $right)
|
4147 |
{
|
4148 |
+
if ($op === '==') {
|
4149 |
+
return static::$false;
|
4150 |
+
}
|
4151 |
+
|
4152 |
+
if ($op === '!=') {
|
4153 |
+
return static::$true;
|
4154 |
+
}
|
4155 |
+
|
4156 |
+
$value = $right->getDimension();
|
4157 |
|
4158 |
return $this->opColorColor(
|
4159 |
$op,
|
4166 |
* Compare number and color
|
4167 |
*
|
4168 |
* @param string $op
|
4169 |
+
* @param Number $left
|
4170 |
* @param array $right
|
4171 |
*
|
4172 |
* @return array
|
4173 |
*/
|
4174 |
+
protected function opNumberColor($op, Number $left, $right)
|
4175 |
{
|
4176 |
+
if ($op === '==') {
|
4177 |
+
return static::$false;
|
4178 |
+
}
|
4179 |
+
|
4180 |
+
if ($op === '!=') {
|
4181 |
+
return static::$true;
|
4182 |
+
}
|
4183 |
+
|
4184 |
+
$value = $left->getDimension();
|
4185 |
|
4186 |
return $this->opColorColor(
|
4187 |
$op,
|
4193 |
/**
|
4194 |
* Compare number1 == number2
|
4195 |
*
|
4196 |
+
* @param array|Number $left
|
4197 |
+
* @param array|Number $right
|
4198 |
*
|
4199 |
* @return array
|
4200 |
*/
|
4214 |
/**
|
4215 |
* Compare number1 != number2
|
4216 |
*
|
4217 |
+
* @param array|Number $left
|
4218 |
+
* @param array|Number $right
|
4219 |
*
|
4220 |
* @return array
|
4221 |
*/
|
4233 |
}
|
4234 |
|
4235 |
/**
|
4236 |
+
* Compare number1 == number2
|
4237 |
*
|
4238 |
+
* @param Number $left
|
4239 |
+
* @param Number $right
|
4240 |
*
|
4241 |
* @return array
|
4242 |
*/
|
4243 |
+
protected function opEqNumberNumber(Number $left, Number $right)
|
4244 |
{
|
4245 |
+
return $this->toBool($left->equals($right));
|
4246 |
}
|
4247 |
|
4248 |
/**
|
4249 |
+
* Compare number1 != number2
|
4250 |
*
|
4251 |
+
* @param Number $left
|
4252 |
+
* @param Number $right
|
4253 |
*
|
4254 |
* @return array
|
4255 |
*/
|
4256 |
+
protected function opNeqNumberNumber(Number $left, Number $right)
|
4257 |
{
|
4258 |
+
return $this->toBool(!$left->equals($right));
|
4259 |
}
|
4260 |
|
4261 |
/**
|
4262 |
+
* Compare number1 >= number2
|
4263 |
*
|
4264 |
+
* @param Number $left
|
4265 |
+
* @param Number $right
|
4266 |
*
|
4267 |
* @return array
|
4268 |
*/
|
4269 |
+
protected function opGteNumberNumber(Number $left, Number $right)
|
4270 |
{
|
4271 |
+
return $this->toBool($left->greaterThanOrEqual($right));
|
4272 |
}
|
4273 |
|
4274 |
/**
|
4275 |
+
* Compare number1 > number2
|
4276 |
*
|
4277 |
+
* @param Number $left
|
4278 |
+
* @param Number $right
|
4279 |
*
|
4280 |
* @return array
|
4281 |
*/
|
4282 |
+
protected function opGtNumberNumber(Number $left, Number $right)
|
4283 |
{
|
4284 |
+
return $this->toBool($left->greaterThan($right));
|
4285 |
}
|
4286 |
|
4287 |
/**
|
4288 |
+
* Compare number1 <= number2
|
4289 |
*
|
4290 |
+
* @param Number $left
|
4291 |
+
* @param Number $right
|
4292 |
*
|
4293 |
+
* @return array
|
4294 |
*/
|
4295 |
+
protected function opLteNumberNumber(Number $left, Number $right)
|
4296 |
{
|
4297 |
+
return $this->toBool($left->lessThanOrEqual($right));
|
4298 |
+
}
|
4299 |
|
4300 |
+
/**
|
4301 |
+
* Compare number1 < number2
|
4302 |
+
*
|
4303 |
+
* @param Number $left
|
4304 |
+
* @param Number $right
|
4305 |
+
*
|
4306 |
+
* @return array
|
4307 |
+
*/
|
4308 |
+
protected function opLtNumberNumber(Number $left, Number $right)
|
4309 |
+
{
|
4310 |
+
return $this->toBool($left->lessThan($right));
|
4311 |
}
|
4312 |
|
4313 |
/**
|
4315 |
*
|
4316 |
* @api
|
4317 |
*
|
4318 |
+
* @param bool $thing
|
4319 |
*
|
4320 |
* @return array
|
4321 |
*/
|
4324 |
return $thing ? static::$true : static::$false;
|
4325 |
}
|
4326 |
|
4327 |
+
/**
|
4328 |
+
* Escape non printable chars in strings output as in dart-sass
|
4329 |
+
*
|
4330 |
+
* @internal
|
4331 |
+
*
|
4332 |
+
* @param string $string
|
4333 |
+
* @param bool $inKeyword
|
4334 |
+
*
|
4335 |
+
* @return string
|
4336 |
+
*/
|
4337 |
+
public function escapeNonPrintableChars($string, $inKeyword = false)
|
4338 |
+
{
|
4339 |
+
static $replacement = [];
|
4340 |
+
if (empty($replacement[$inKeyword])) {
|
4341 |
+
for ($i = 0; $i < 32; $i++) {
|
4342 |
+
if ($i !== 9 || $inKeyword) {
|
4343 |
+
$replacement[$inKeyword][chr($i)] = '\\' . dechex($i) . ($inKeyword ? ' ' : chr(0));
|
4344 |
+
}
|
4345 |
+
}
|
4346 |
+
}
|
4347 |
+
$string = str_replace(array_keys($replacement[$inKeyword]), array_values($replacement[$inKeyword]), $string);
|
4348 |
+
// chr(0) is not a possible char from the input, so any chr(0) comes from our escaping replacement
|
4349 |
+
if (strpos($string, chr(0)) !== false) {
|
4350 |
+
if (substr($string, -1) === chr(0)) {
|
4351 |
+
$string = substr($string, 0, -1);
|
4352 |
+
}
|
4353 |
+
$string = str_replace(
|
4354 |
+
[chr(0) . '\\',chr(0) . ' '],
|
4355 |
+
[ '\\', ' '],
|
4356 |
+
$string
|
4357 |
+
);
|
4358 |
+
if (strpos($string, chr(0)) !== false) {
|
4359 |
+
$parts = explode(chr(0), $string);
|
4360 |
+
$string = array_shift($parts);
|
4361 |
+
while (count($parts)) {
|
4362 |
+
$next = array_shift($parts);
|
4363 |
+
if (strpos("0123456789abcdefABCDEF" . chr(9), $next[0]) !== false) {
|
4364 |
+
$string .= " ";
|
4365 |
+
}
|
4366 |
+
$string .= $next;
|
4367 |
+
}
|
4368 |
+
}
|
4369 |
+
}
|
4370 |
+
|
4371 |
+
return $string;
|
4372 |
+
}
|
4373 |
+
|
4374 |
/**
|
4375 |
* Compiles a primitive value into a CSS property value.
|
4376 |
*
|
4384 |
*
|
4385 |
* @api
|
4386 |
*
|
4387 |
+
* @param array|Number $value
|
4388 |
+
* @param bool $quote
|
4389 |
*
|
4390 |
+
* @return string
|
4391 |
*/
|
4392 |
+
public function compileValue($value, $quote = true)
|
4393 |
{
|
4394 |
$value = $this->reduce($value);
|
4395 |
|
4396 |
+
if ($value instanceof Number) {
|
4397 |
+
return $value->output($this);
|
4398 |
+
}
|
4399 |
+
|
4400 |
switch ($value[0]) {
|
4401 |
case Type::T_KEYWORD:
|
4402 |
+
return $this->escapeNonPrintableChars($value[1], true);
|
4403 |
|
4404 |
case Type::T_COLOR:
|
4405 |
// [1] - red component (either number for a %)
|
4423 |
}
|
4424 |
|
4425 |
if (is_numeric($alpha)) {
|
4426 |
+
$a = new Number($alpha, '');
|
4427 |
} else {
|
4428 |
$a = $alpha;
|
4429 |
}
|
4451 |
|
4452 |
return $h;
|
4453 |
|
|
|
|
|
|
|
4454 |
case Type::T_STRING:
|
4455 |
+
$content = $this->compileStringContent($value, $quote);
|
4456 |
+
|
4457 |
+
if ($value[1] && $quote) {
|
4458 |
+
$content = str_replace('\\', '\\\\', $content);
|
4459 |
+
|
4460 |
+
$content = $this->escapeNonPrintableChars($content);
|
4461 |
|
|
|
4462 |
// force double quote as string quote for the output in certain cases
|
4463 |
if (
|
4464 |
$value[1] === "'" &&
|
4465 |
+
(strpos($content, '"') === false or strpos($content, "'") !== false) &&
|
4466 |
+
strpbrk($content, '{}\\\'') !== false
|
4467 |
) {
|
4468 |
$value[1] = '"';
|
4469 |
+
} elseif (
|
4470 |
+
$value[1] === '"' &&
|
4471 |
+
(strpos($content, '"') !== false and strpos($content, "'") === false)
|
4472 |
+
) {
|
4473 |
+
$value[1] = "'";
|
4474 |
}
|
4475 |
+
|
4476 |
+
$content = str_replace($value[1], '\\' . $value[1], $content);
|
|
|
|
|
|
|
4477 |
}
|
4478 |
|
4479 |
return $value[1] . $content . $value[1];
|
4480 |
|
4481 |
case Type::T_FUNCTION:
|
4482 |
+
$args = ! empty($value[2]) ? $this->compileValue($value[2], $quote) : '';
|
4483 |
|
4484 |
return "$value[1]($args)";
|
4485 |
|
4492 |
$value = $this->extractInterpolation($value);
|
4493 |
|
4494 |
if ($value[0] !== Type::T_LIST) {
|
4495 |
+
return $this->compileValue($value, $quote);
|
4496 |
}
|
4497 |
|
4498 |
list(, $delim, $items) = $value;
|
4524 |
|
4525 |
$filtered = [];
|
4526 |
|
4527 |
+
$same_string_quote = null;
|
4528 |
foreach ($items as $item) {
|
4529 |
+
if (\is_null($same_string_quote)) {
|
4530 |
+
$same_string_quote = false;
|
4531 |
+
if ($item[0] === Type::T_STRING) {
|
4532 |
+
$same_string_quote = $item[1];
|
4533 |
+
foreach ($items as $ii) {
|
4534 |
+
if ($ii[0] !== Type::T_STRING) {
|
4535 |
+
$same_string_quote = false;
|
4536 |
+
break;
|
4537 |
+
}
|
4538 |
+
}
|
4539 |
+
}
|
4540 |
+
}
|
4541 |
if ($item[0] === Type::T_NULL) {
|
4542 |
continue;
|
4543 |
}
|
4544 |
+
if ($same_string_quote === '"' && $item[0] === Type::T_STRING && $item[1]) {
|
4545 |
+
$item[1] = $same_string_quote;
|
4546 |
+
}
|
4547 |
|
4548 |
+
$compiled = $this->compileValue($item, $quote);
|
4549 |
|
4550 |
if ($prefix_value && \strlen($compiled)) {
|
4551 |
$compiled = $prefix_value . $compiled;
|
4562 |
$filtered = [];
|
4563 |
|
4564 |
for ($i = 0, $s = \count($keys); $i < $s; $i++) {
|
4565 |
+
$filtered[$this->compileValue($keys[$i], $quote)] = $this->compileValue($values[$i], $quote);
|
4566 |
}
|
4567 |
|
4568 |
array_walk($filtered, function (&$value, $key) {
|
4583 |
}
|
4584 |
|
4585 |
$left = \count($left[2]) > 0
|
4586 |
+
? $this->compileValue($left, $quote) . $delim . $whiteLeft
|
4587 |
: '';
|
4588 |
|
4589 |
$delim = $right[1];
|
4593 |
}
|
4594 |
|
4595 |
$right = \count($right[2]) > 0 ?
|
4596 |
+
$whiteRight . $delim . $this->compileValue($right, $quote) : '';
|
4597 |
|
4598 |
+
return $left . $this->compileValue($interpolate, $quote) . $right;
|
4599 |
|
4600 |
case Type::T_INTERPOLATE:
|
4601 |
// strip quotes if it's a string
|
4602 |
$reduced = $this->reduce($value[1]);
|
4603 |
|
4604 |
+
if ($reduced instanceof Number) {
|
4605 |
+
return $this->compileValue($reduced, $quote);
|
4606 |
+
}
|
4607 |
+
|
4608 |
switch ($reduced[0]) {
|
4609 |
case Type::T_LIST:
|
4610 |
$reduced = $this->extractInterpolation($reduced);
|
4626 |
continue;
|
4627 |
}
|
4628 |
|
4629 |
+
if ($item[0] === Type::T_STRING) {
|
4630 |
+
$filtered[] = $this->compileStringContent($item, $quote);
|
4631 |
+
} elseif ($item[0] === Type::T_KEYWORD) {
|
4632 |
+
$filtered[] = $item[1];
|
|
|
|
|
4633 |
} else {
|
4634 |
+
$filtered[] = $this->compileValue($item, $quote);
|
4635 |
}
|
4636 |
}
|
4637 |
|
4639 |
break;
|
4640 |
|
4641 |
case Type::T_STRING:
|
4642 |
+
$reduced = [Type::T_STRING, '', [$this->compileStringContent($reduced)]];
|
4643 |
break;
|
4644 |
|
4645 |
case Type::T_NULL:
|
4646 |
$reduced = [Type::T_KEYWORD, ''];
|
4647 |
}
|
4648 |
|
4649 |
+
return $this->compileValue($reduced, $quote);
|
4650 |
|
4651 |
case Type::T_NULL:
|
4652 |
return 'null';
|
4660 |
}
|
4661 |
|
4662 |
/**
|
4663 |
+
* @param array|Number $value
|
4664 |
*
|
4665 |
+
* @return string
|
4666 |
*/
|
4667 |
protected function compileDebugValue($value)
|
4668 |
{
|
4669 |
$value = $this->reduce($value, true);
|
4670 |
|
4671 |
+
if ($value instanceof Number) {
|
4672 |
+
return $this->compileValue($value);
|
4673 |
+
}
|
4674 |
+
|
4675 |
switch ($value[0]) {
|
4676 |
case Type::T_STRING:
|
4677 |
return $this->compileStringContent($value);
|
4687 |
* @param array $list
|
4688 |
*
|
4689 |
* @return string
|
4690 |
+
*
|
4691 |
+
* @deprecated
|
4692 |
*/
|
4693 |
protected function flattenList($list)
|
4694 |
{
|
4695 |
+
@trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
|
4696 |
+
|
4697 |
return $this->compileValue($list);
|
4698 |
}
|
4699 |
|
4700 |
/**
|
4701 |
+
* Gets the text of a Sass string
|
4702 |
*
|
4703 |
+
* Calling this method on anything else than a SassString is unsupported. Use {@see assertString} first
|
4704 |
+
* to ensure that the value is indeed a string.
|
4705 |
+
*
|
4706 |
+
* @param array $value
|
4707 |
*
|
4708 |
* @return string
|
4709 |
*/
|
4710 |
+
public function getStringText(array $value)
|
4711 |
{
|
4712 |
+
if ($value[0] !== Type::T_STRING) {
|
4713 |
+
throw new \InvalidArgumentException('The argument is not a sass string. Did you forgot to use "assertString"?');
|
4714 |
+
}
|
4715 |
|
4716 |
+
return $this->compileStringContent($value);
|
4717 |
+
}
|
4718 |
+
|
4719 |
+
/**
|
4720 |
+
* Compile string content
|
4721 |
+
*
|
4722 |
+
* @param array $string
|
4723 |
+
* @param bool $quote
|
4724 |
+
*
|
4725 |
+
* @return string
|
4726 |
+
*/
|
4727 |
+
protected function compileStringContent($string, $quote = true)
|
4728 |
+
{
|
4729 |
+
$parts = [];
|
4730 |
+
|
4731 |
+
foreach ($string[2] as $part) {
|
4732 |
+
if (\is_array($part) || $part instanceof Number) {
|
4733 |
+
$parts[] = $this->compileValue($part, $quote);
|
4734 |
+
} else {
|
4735 |
+
$parts[] = $part;
|
4736 |
}
|
4737 |
}
|
4738 |
|
4942 |
/**
|
4943 |
* Convert env linked list to stack
|
4944 |
*
|
4945 |
+
* @param Environment $env
|
4946 |
*
|
4947 |
+
* @return Environment[]
|
4948 |
+
*
|
4949 |
+
* @phpstan-return non-empty-array<Environment>
|
4950 |
*/
|
4951 |
protected function compactEnv(Environment $env)
|
4952 |
{
|
4960 |
/**
|
4961 |
* Convert env stack to singly linked list
|
4962 |
*
|
4963 |
+
* @param Environment[] $envs
|
4964 |
*
|
4965 |
+
* @return Environment
|
4966 |
+
*
|
4967 |
+
* @phpstan-param non-empty-array<Environment> $envs
|
4968 |
*/
|
4969 |
protected function extractEnv($envs)
|
4970 |
{
|
5000 |
|
5001 |
/**
|
5002 |
* Pop environment
|
5003 |
+
*
|
5004 |
+
* @return void
|
5005 |
*/
|
5006 |
protected function popEnv()
|
5007 |
{
|
5012 |
/**
|
5013 |
* Propagate vars from a just poped Env (used in @each and @for)
|
5014 |
*
|
5015 |
+
* @param array $store
|
5016 |
+
* @param null|string[] $excludedVars
|
5017 |
+
*
|
5018 |
+
* @return void
|
5019 |
*/
|
5020 |
protected function backPropagateEnv($store, $excludedVars = null)
|
5021 |
{
|
5044 |
* @param boolean $shadow
|
5045 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
5046 |
* @param mixed $valueUnreduced
|
5047 |
+
*
|
5048 |
+
* @return void
|
5049 |
*/
|
5050 |
protected function set($name, $value, $shadow = false, Environment $env = null, $valueUnreduced = null)
|
5051 |
{
|
5069 |
* @param mixed $value
|
5070 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
5071 |
* @param mixed $valueUnreduced
|
5072 |
+
*
|
5073 |
+
* @return void
|
5074 |
*/
|
5075 |
protected function setExisting($name, $value, Environment $env, $valueUnreduced = null)
|
5076 |
{
|
5129 |
* @param mixed $value
|
5130 |
* @param \ScssPhp\ScssPhp\Compiler\Environment $env
|
5131 |
* @param mixed $valueUnreduced
|
5132 |
+
*
|
5133 |
+
* @return void
|
5134 |
*/
|
5135 |
protected function setRaw($name, $value, Environment $env, $valueUnreduced = null)
|
5136 |
{
|
5144 |
/**
|
5145 |
* Get variable
|
5146 |
*
|
5147 |
+
* @internal
|
5148 |
*
|
5149 |
* @param string $name
|
5150 |
* @param boolean $shouldThrow
|
5227 |
* Inject variables
|
5228 |
*
|
5229 |
* @param array $args
|
5230 |
+
*
|
5231 |
+
* @return void
|
5232 |
*/
|
5233 |
protected function injectVariables(array $args)
|
5234 |
{
|
5243 |
$name = substr($name, 1);
|
5244 |
}
|
5245 |
|
5246 |
+
if (!\is_string($strValue) || ! $parser->parseValue($strValue, $value)) {
|
5247 |
$value = $this->coerceValue($strValue);
|
5248 |
}
|
5249 |
|
5251 |
}
|
5252 |
}
|
5253 |
|
5254 |
+
/**
|
5255 |
+
* Replaces variables.
|
5256 |
+
*
|
5257 |
+
* @param array<string, mixed> $variables
|
5258 |
+
*
|
5259 |
+
* @return void
|
5260 |
+
*/
|
5261 |
+
public function replaceVariables(array $variables)
|
5262 |
+
{
|
5263 |
+
$this->registeredVars = [];
|
5264 |
+
$this->addVariables($variables);
|
5265 |
+
}
|
5266 |
+
|
5267 |
+
/**
|
5268 |
+
* Replaces variables.
|
5269 |
+
*
|
5270 |
+
* @param array<string, mixed> $variables
|
5271 |
+
*
|
5272 |
+
* @return void
|
5273 |
+
*/
|
5274 |
+
public function addVariables(array $variables)
|
5275 |
+
{
|
5276 |
+
$triggerWarning = false;
|
5277 |
+
|
5278 |
+
foreach ($variables as $name => $value) {
|
5279 |
+
if (!$value instanceof Number && !\is_array($value)) {
|
5280 |
+
$triggerWarning = true;
|
5281 |
+
}
|
5282 |
+
|
5283 |
+
$this->registeredVars[$name] = $value;
|
5284 |
+
}
|
5285 |
+
|
5286 |
+
if ($triggerWarning) {
|
5287 |
+
@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);
|
5288 |
+
}
|
5289 |
+
}
|
5290 |
+
|
5291 |
/**
|
5292 |
* Set variables
|
5293 |
*
|
5294 |
* @api
|
5295 |
*
|
5296 |
* @param array $variables
|
5297 |
+
*
|
5298 |
+
* @return void
|
5299 |
+
*
|
5300 |
+
* @deprecated Use "addVariables" or "replaceVariables" instead.
|
5301 |
*/
|
5302 |
public function setVariables(array $variables)
|
5303 |
{
|
5304 |
+
@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.');
|
5305 |
+
|
5306 |
+
$this->addVariables($variables);
|
5307 |
}
|
5308 |
|
5309 |
/**
|
5312 |
* @api
|
5313 |
*
|
5314 |
* @param string $name
|
5315 |
+
*
|
5316 |
+
* @return void
|
5317 |
*/
|
5318 |
public function unsetVariable($name)
|
5319 |
{
|
5335 |
/**
|
5336 |
* Adds to list of parsed files
|
5337 |
*
|
5338 |
+
* @internal
|
5339 |
*
|
5340 |
+
* @param string|null $path
|
5341 |
+
*
|
5342 |
+
* @return void
|
5343 |
*/
|
5344 |
public function addParsedFile($path)
|
5345 |
{
|
5346 |
+
if (! \is_null($path) && is_file($path)) {
|
5347 |
$this->parsedFiles[realpath($path)] = filemtime($path);
|
5348 |
}
|
5349 |
}
|
5351 |
/**
|
5352 |
* Returns list of parsed files
|
5353 |
*
|
5354 |
+
* @deprecated
|
5355 |
+
* @return array<string, int>
|
|
|
5356 |
*/
|
5357 |
public function getParsedFiles()
|
5358 |
{
|
5359 |
+
@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);
|
5360 |
return $this->parsedFiles;
|
5361 |
}
|
5362 |
|
5366 |
* @api
|
5367 |
*
|
5368 |
* @param string|callable $path
|
5369 |
+
*
|
5370 |
+
* @return void
|
5371 |
*/
|
5372 |
public function addImportPath($path)
|
5373 |
{
|
5381 |
*
|
5382 |
* @api
|
5383 |
*
|
5384 |
+
* @param string|array<string|callable> $path
|
5385 |
+
*
|
5386 |
+
* @return void
|
5387 |
*/
|
5388 |
public function setImportPaths($path)
|
5389 |
{
|
5390 |
+
$paths = (array) $path;
|
5391 |
+
$actualImportPaths = array_filter($paths, function ($path) {
|
5392 |
+
return $path !== '';
|
5393 |
+
});
|
5394 |
+
|
5395 |
+
$this->legacyCwdImportPath = \count($actualImportPaths) !== \count($paths);
|
5396 |
+
|
5397 |
+
if ($this->legacyCwdImportPath) {
|
5398 |
+
@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);
|
5399 |
+
}
|
5400 |
+
|
5401 |
+
$this->importPaths = $actualImportPaths;
|
5402 |
}
|
5403 |
|
5404 |
/**
|
5408 |
*
|
5409 |
* @param integer $numberPrecision
|
5410 |
*
|
5411 |
+
* @return void
|
5412 |
+
*
|
5413 |
* @deprecated The number precision is not configurable anymore. The default is enough for all browsers.
|
5414 |
*/
|
5415 |
public function setNumberPrecision($numberPrecision)
|
5418 |
. 'The default is enough for all browsers.', E_USER_DEPRECATED);
|
5419 |
}
|
5420 |
|
5421 |
+
/**
|
5422 |
+
* Sets the output style.
|
5423 |
+
*
|
5424 |
+
* @api
|
5425 |
+
*
|
5426 |
+
* @param string $style One of the OutputStyle constants
|
5427 |
+
*
|
5428 |
+
* @return void
|
5429 |
+
*
|
5430 |
+
* @phpstan-param OutputStyle::* $style
|
5431 |
+
*/
|
5432 |
+
public function setOutputStyle($style)
|
5433 |
+
{
|
5434 |
+
switch ($style) {
|
5435 |
+
case OutputStyle::EXPANDED:
|
5436 |
+
$this->formatter = Expanded::class;
|
5437 |
+
break;
|
5438 |
+
|
5439 |
+
case OutputStyle::COMPRESSED:
|
5440 |
+
$this->formatter = Compressed::class;
|
5441 |
+
break;
|
5442 |
+
|
5443 |
+
default:
|
5444 |
+
throw new \InvalidArgumentException(sprintf('Invalid output style "%s".', $style));
|
5445 |
+
}
|
5446 |
+
}
|
5447 |
+
|
5448 |
/**
|
5449 |
* Set formatter
|
5450 |
*
|
5451 |
* @api
|
5452 |
*
|
5453 |
* @param string $formatterName
|
5454 |
+
*
|
5455 |
+
* @return void
|
5456 |
+
*
|
5457 |
+
* @deprecated Use {@see setOutputStyle} instead.
|
5458 |
*/
|
5459 |
public function setFormatter($formatterName)
|
5460 |
{
|
5461 |
+
if (!\in_array($formatterName, [Expanded::class, Compressed::class], true)) {
|
5462 |
+
@trigger_error('Formatters other than Expanded and Compressed are deprecated.', E_USER_DEPRECATED);
|
5463 |
+
}
|
5464 |
+
@trigger_error('The method "setFormatter" is deprecated. Use "setOutputStyle" instead.', E_USER_DEPRECATED);
|
5465 |
+
|
5466 |
$this->formatter = $formatterName;
|
5467 |
}
|
5468 |
|
5472 |
* @api
|
5473 |
*
|
5474 |
* @param string $lineNumberStyle
|
5475 |
+
*
|
5476 |
+
* @return void
|
5477 |
+
*
|
5478 |
+
* @deprecated The line number output is not supported anymore. Use source maps instead.
|
5479 |
*/
|
5480 |
public function setLineNumberStyle($lineNumberStyle)
|
5481 |
{
|
5482 |
+
@trigger_error('The line number output is not supported anymore. '
|
5483 |
+
. 'Use source maps instead.', E_USER_DEPRECATED);
|
5484 |
}
|
5485 |
|
5486 |
/**
|
5489 |
* @api
|
5490 |
*
|
5491 |
* @param integer $sourceMap
|
5492 |
+
*
|
5493 |
+
* @return void
|
5494 |
+
*
|
5495 |
+
* @phpstan-param self::SOURCE_MAP_* $sourceMap
|
5496 |
*/
|
5497 |
public function setSourceMap($sourceMap)
|
5498 |
{
|
5505 |
* @api
|
5506 |
*
|
5507 |
* @param array $sourceMapOptions
|
5508 |
+
*
|
5509 |
+
* @phpstan-param array{sourceRoot?: string, sourceMapFilename?: string|null, sourceMapURL?: string|null, sourceMapWriteTo?: string|null, outputSourceFiles?: bool, sourceMapRootpath?: string, sourceMapBasepath?: string} $sourceMapOptions
|
5510 |
+
*
|
5511 |
+
* @return void
|
5512 |
*/
|
5513 |
public function setSourceMapOptions($sourceMapOptions)
|
5514 |
{
|
5520 |
*
|
5521 |
* @api
|
5522 |
*
|
5523 |
+
* @param string $name
|
5524 |
+
* @param callable $callback
|
5525 |
+
* @param string[]|null $argumentDeclaration
|
5526 |
+
*
|
5527 |
+
* @return void
|
5528 |
*/
|
5529 |
+
public function registerFunction($name, $callback, $argumentDeclaration = null)
|
5530 |
{
|
5531 |
+
if (self::isNativeFunction($name)) {
|
5532 |
+
@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);
|
5533 |
+
}
|
5534 |
+
|
5535 |
+
if ($argumentDeclaration === null) {
|
5536 |
+
@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);
|
5537 |
+
}
|
5538 |
+
|
5539 |
+
$this->userFunctions[$this->normalizeName($name)] = [$callback, $argumentDeclaration];
|
5540 |
}
|
5541 |
|
5542 |
/**
|
5545 |
* @api
|
5546 |
*
|
5547 |
* @param string $name
|
5548 |
+
*
|
5549 |
+
* @return void
|
5550 |
*/
|
5551 |
public function unregisterFunction($name)
|
5552 |
{
|
5559 |
* @api
|
5560 |
*
|
5561 |
* @param string $name
|
5562 |
+
*
|
5563 |
+
* @return void
|
5564 |
+
*
|
5565 |
+
* @deprecated Registering additional features is deprecated.
|
5566 |
*/
|
5567 |
public function addFeature($name)
|
5568 |
{
|
5569 |
+
@trigger_error('Registering additional features is deprecated.', E_USER_DEPRECATED);
|
5570 |
+
|
5571 |
$this->registeredFeatures[$name] = true;
|
5572 |
}
|
5573 |
|
5576 |
*
|
5577 |
* @param string $path
|
5578 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $out
|
5579 |
+
*
|
5580 |
+
* @return void
|
5581 |
*/
|
5582 |
protected function importFile($path, OutputBlock $out)
|
5583 |
{
|
5584 |
+
$this->pushCallStack('import ' . $this->getPrettyPath($path));
|
5585 |
// see if tree is cached
|
5586 |
$realPath = realpath($path);
|
5587 |
|
5588 |
+
if (substr($path, -5) === '.sass') {
|
5589 |
+
$this->sourceIndex = \count($this->sourceNames);
|
5590 |
+
$this->sourceNames[] = $path;
|
5591 |
+
$this->sourceLine = 1;
|
5592 |
+
$this->sourceColumn = 1;
|
5593 |
+
|
5594 |
+
throw $this->error('The Sass indented syntax is not implemented.');
|
5595 |
+
}
|
5596 |
+
|
5597 |
if (isset($this->importCache[$realPath])) {
|
5598 |
$this->handleImportLoop($realPath);
|
5599 |
|
5606 |
$this->importCache[$realPath] = $tree;
|
5607 |
}
|
5608 |
|
5609 |
+
$currentDirectory = $this->currentDirectory;
|
5610 |
+
$this->currentDirectory = dirname($path);
|
5611 |
|
|
|
5612 |
$this->compileChildrenNoReturn($tree->children, $out);
|
5613 |
+
$this->currentDirectory = $currentDirectory;
|
5614 |
$this->popCallStack();
|
5615 |
}
|
5616 |
|
5617 |
/**
|
5618 |
+
* Save the imported files with their resolving path context
|
5619 |
*
|
5620 |
+
* @param string|null $currentDirectory
|
5621 |
+
* @param string $path
|
5622 |
+
* @param string $filePath
|
5623 |
+
*
|
5624 |
+
* @return void
|
5625 |
+
*/
|
5626 |
+
private function registerImport($currentDirectory, $path, $filePath)
|
5627 |
+
{
|
5628 |
+
$this->resolvedImports[] = ['currentDir' => $currentDirectory, 'path' => $path, 'filePath' => $filePath];
|
5629 |
+
}
|
5630 |
+
|
5631 |
+
/**
|
5632 |
+
* Detects whether the import is a CSS import.
|
5633 |
+
*
|
5634 |
+
* For legacy reasons, custom importers are called for those, allowing them
|
5635 |
+
* to replace them with an actual Sass import. However this behavior is
|
5636 |
+
* deprecated. Custom importers are expected to return null when they receive
|
5637 |
+
* a CSS import.
|
5638 |
*
|
5639 |
* @param string $url
|
5640 |
*
|
5641 |
+
* @return bool
|
5642 |
*/
|
5643 |
+
public static function isCssImport($url)
|
5644 |
{
|
5645 |
+
return 1 === preg_match('~\.css$|^https?://|^//~', $url);
|
5646 |
+
}
|
5647 |
|
5648 |
+
/**
|
5649 |
+
* Return the file path for an import url if it exists
|
5650 |
+
*
|
5651 |
+
* @internal
|
5652 |
+
*
|
5653 |
+
* @param string $url
|
5654 |
+
* @param string|null $currentDir
|
5655 |
+
*
|
5656 |
+
* @return string|null
|
5657 |
+
*/
|
5658 |
+
public function findImport($url, $currentDir = null)
|
5659 |
+
{
|
5660 |
+
// Vanilla css and external requests. These are not meant to be Sass imports.
|
5661 |
+
// Callback importers are still called for BC.
|
5662 |
+
if (self::isCssImport($url)) {
|
5663 |
+
foreach ($this->importPaths as $dir) {
|
5664 |
+
if (\is_string($dir)) {
|
5665 |
+
continue;
|
5666 |
+
}
|
5667 |
|
5668 |
+
if (\is_callable($dir)) {
|
5669 |
+
// check custom callback for import path
|
5670 |
+
$file = \call_user_func($dir, $url);
|
5671 |
|
5672 |
+
if (! \is_null($file)) {
|
5673 |
+
if (\is_array($dir)) {
|
5674 |
+
$callableDescription = (\is_object($dir[0]) ? \get_class($dir[0]) : $dir[0]).'::'.$dir[1];
|
5675 |
+
} elseif ($dir instanceof \Closure) {
|
5676 |
+
$r = new \ReflectionFunction($dir);
|
5677 |
+
if (false !== strpos($r->name, '{closure}')) {
|
5678 |
+
$callableDescription = sprintf('closure{%s:%s}', $r->getFileName(), $r->getStartLine());
|
5679 |
+
} elseif ($class = $r->getClosureScopeClass()) {
|
5680 |
+
$callableDescription = $class->name.'::'.$r->name;
|
5681 |
+
} else {
|
5682 |
+
$callableDescription = $r->name;
|
5683 |
+
}
|
5684 |
+
} elseif (\is_object($dir)) {
|
5685 |
+
$callableDescription = \get_class($dir) . '::__invoke';
|
5686 |
+
} else {
|
5687 |
+
$callableDescription = 'callable'; // Fallback if we don't have a dedicated description
|
5688 |
+
}
|
5689 |
+
@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);
|
5690 |
|
5691 |
+
return $file;
|
5692 |
+
}
|
5693 |
+
}
|
5694 |
}
|
5695 |
+
return null;
|
5696 |
+
}
|
5697 |
|
5698 |
+
if (!\is_null($currentDir)) {
|
5699 |
+
$relativePath = $this->resolveImportPath($url, $currentDir);
|
5700 |
+
|
5701 |
+
if (!\is_null($relativePath)) {
|
5702 |
+
return $relativePath;
|
5703 |
}
|
5704 |
}
|
5705 |
|
5706 |
foreach ($this->importPaths as $dir) {
|
5707 |
if (\is_string($dir)) {
|
5708 |
+
$path = $this->resolveImportPath($url, $dir);
|
5709 |
+
|
5710 |
+
if (!\is_null($path)) {
|
5711 |
+
return $path;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5712 |
}
|
5713 |
} elseif (\is_callable($dir)) {
|
5714 |
// check custom callback for import path
|
5720 |
}
|
5721 |
}
|
5722 |
|
5723 |
+
if ($this->legacyCwdImportPath) {
|
5724 |
+
$path = $this->resolveImportPath($url, getcwd());
|
5725 |
+
|
5726 |
+
if (!\is_null($path)) {
|
5727 |
+
@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);
|
5728 |
+
|
5729 |
+
return $path;
|
5730 |
}
|
5731 |
}
|
5732 |
|
5733 |
+
throw $this->error("`$url` file not found for @import");
|
5734 |
+
}
|
5735 |
+
|
5736 |
+
/**
|
5737 |
+
* @param string $url
|
5738 |
+
* @param string $baseDir
|
5739 |
+
*
|
5740 |
+
* @return string|null
|
5741 |
+
*/
|
5742 |
+
private function resolveImportPath($url, $baseDir)
|
5743 |
+
{
|
5744 |
+
$path = Path::join($baseDir, $url);
|
5745 |
+
|
5746 |
+
$hasExtension = preg_match('/.s[ac]ss$/', $url);
|
5747 |
+
|
5748 |
+
if ($hasExtension) {
|
5749 |
+
return $this->checkImportPathConflicts($this->tryImportPath($path));
|
5750 |
+
}
|
5751 |
+
|
5752 |
+
$result = $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path));
|
5753 |
+
|
5754 |
+
if (!\is_null($result)) {
|
5755 |
+
return $result;
|
5756 |
+
}
|
5757 |
+
|
5758 |
+
return $this->tryImportPathAsDirectory($path);
|
5759 |
+
}
|
5760 |
+
|
5761 |
+
/**
|
5762 |
+
* @param string[] $paths
|
5763 |
+
*
|
5764 |
+
* @return string|null
|
5765 |
+
*/
|
5766 |
+
private function checkImportPathConflicts(array $paths)
|
5767 |
+
{
|
5768 |
+
if (\count($paths) === 0) {
|
5769 |
+
return null;
|
5770 |
+
}
|
5771 |
+
|
5772 |
+
if (\count($paths) === 1) {
|
5773 |
+
return $paths[0];
|
5774 |
+
}
|
5775 |
+
|
5776 |
+
$formattedPrettyPaths = [];
|
5777 |
+
|
5778 |
+
foreach ($paths as $path) {
|
5779 |
+
$formattedPrettyPaths[] = ' ' . $this->getPrettyPath($path);
|
5780 |
+
}
|
5781 |
+
|
5782 |
+
throw $this->error("It's not clear which file to import. Found:\n" . implode("\n", $formattedPrettyPaths));
|
5783 |
+
}
|
5784 |
+
|
5785 |
+
/**
|
5786 |
+
* @param string $path
|
5787 |
+
*
|
5788 |
+
* @return string[]
|
5789 |
+
*/
|
5790 |
+
private function tryImportPathWithExtensions($path)
|
5791 |
+
{
|
5792 |
+
$result = array_merge(
|
5793 |
+
$this->tryImportPath($path.'.sass'),
|
5794 |
+
$this->tryImportPath($path.'.scss')
|
5795 |
+
);
|
5796 |
+
|
5797 |
+
if ($result) {
|
5798 |
+
return $result;
|
5799 |
+
}
|
5800 |
+
|
5801 |
+
return $this->tryImportPath($path.'.css');
|
5802 |
+
}
|
5803 |
+
|
5804 |
+
/**
|
5805 |
+
* @param string $path
|
5806 |
+
*
|
5807 |
+
* @return string[]
|
5808 |
+
*/
|
5809 |
+
private function tryImportPath($path)
|
5810 |
+
{
|
5811 |
+
$partial = dirname($path).'/_'.basename($path);
|
5812 |
+
|
5813 |
+
$candidates = [];
|
5814 |
+
|
5815 |
+
if (is_file($partial)) {
|
5816 |
+
$candidates[] = $partial;
|
5817 |
+
}
|
5818 |
+
|
5819 |
+
if (is_file($path)) {
|
5820 |
+
$candidates[] = $path;
|
5821 |
+
}
|
5822 |
+
|
5823 |
+
return $candidates;
|
5824 |
+
}
|
5825 |
+
|
5826 |
+
/**
|
5827 |
+
* @param string $path
|
5828 |
+
*
|
5829 |
+
* @return string|null
|
5830 |
+
*/
|
5831 |
+
private function tryImportPathAsDirectory($path)
|
5832 |
+
{
|
5833 |
+
if (!is_dir($path)) {
|
5834 |
+
return null;
|
5835 |
+
}
|
5836 |
+
|
5837 |
+
return $this->checkImportPathConflicts($this->tryImportPathWithExtensions($path.'/index'));
|
5838 |
+
}
|
5839 |
+
|
5840 |
+
/**
|
5841 |
+
* @param string|null $path
|
5842 |
+
*
|
5843 |
+
* @return string
|
5844 |
+
*/
|
5845 |
+
private function getPrettyPath($path)
|
5846 |
+
{
|
5847 |
+
if ($path === null) {
|
5848 |
+
return '(unknown file)';
|
5849 |
+
}
|
5850 |
+
|
5851 |
+
$normalizedPath = $path;
|
5852 |
+
$normalizedRootDirectory = $this->rootDirectory.'/';
|
5853 |
+
|
5854 |
+
if (\DIRECTORY_SEPARATOR === '\\') {
|
5855 |
+
$normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory);
|
5856 |
+
$normalizedPath = str_replace('\\', '/', $path);
|
5857 |
+
}
|
5858 |
+
|
5859 |
+
if (0 === strpos($normalizedPath, $normalizedRootDirectory)) {
|
5860 |
+
return substr($normalizedPath, \strlen($normalizedRootDirectory));
|
5861 |
+
}
|
5862 |
+
|
5863 |
+
return $path;
|
5864 |
}
|
5865 |
|
5866 |
/**
|
5868 |
*
|
5869 |
* @api
|
5870 |
*
|
5871 |
+
* @param string|null $encoding
|
5872 |
+
*
|
5873 |
+
* @return void
|
5874 |
+
*
|
5875 |
+
* @deprecated Non-compliant support for other encodings than UTF-8 is deprecated.
|
5876 |
*/
|
5877 |
public function setEncoding($encoding)
|
5878 |
{
|
5879 |
+
if (!$encoding || strtolower($encoding) === 'utf-8') {
|
5880 |
+
@trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
|
5881 |
+
} else {
|
5882 |
+
@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);
|
5883 |
+
}
|
5884 |
+
|
5885 |
$this->encoding = $encoding;
|
5886 |
}
|
5887 |
|
5909 |
* @api
|
5910 |
*
|
5911 |
* @return array
|
5912 |
+
*
|
5913 |
+
* @deprecated
|
5914 |
*/
|
5915 |
public function getSourcePosition()
|
5916 |
{
|
5917 |
+
@trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
|
5918 |
+
|
5919 |
$sourceFile = isset($this->sourceNames[$this->sourceIndex]) ? $this->sourceNames[$this->sourceIndex] : '';
|
5920 |
|
5921 |
return [$sourceFile, $this->sourceLine, $this->sourceColumn];
|
5945 |
/**
|
5946 |
* Build an error (exception)
|
5947 |
*
|
5948 |
+
* @internal
|
5949 |
*
|
5950 |
* @param string $msg Message with optional sprintf()-style vararg parameters
|
5951 |
*
|
5958 |
}
|
5959 |
|
5960 |
if (! $this->ignoreCallStackMessage) {
|
5961 |
+
$msg = $this->addLocationToMessage($msg);
|
5962 |
+
}
|
5963 |
+
|
5964 |
+
return new CompilerException($msg);
|
5965 |
+
}
|
5966 |
+
|
5967 |
+
/**
|
5968 |
+
* @param string $msg
|
5969 |
+
*
|
5970 |
+
* @return string
|
5971 |
+
*/
|
5972 |
+
private function addLocationToMessage($msg)
|
5973 |
+
{
|
5974 |
+
$line = $this->sourceLine;
|
5975 |
+
$column = $this->sourceColumn;
|
5976 |
|
5977 |
+
$loc = isset($this->sourceNames[$this->sourceIndex])
|
5978 |
+
? $this->getPrettyPath($this->sourceNames[$this->sourceIndex]) . " on line $line, at column $column"
|
5979 |
+
: "line: $line, column: $column";
|
5980 |
|
5981 |
+
$msg = "$msg: $loc";
|
5982 |
|
5983 |
+
$callStackMsg = $this->callStackMessage();
|
5984 |
|
5985 |
+
if ($callStackMsg) {
|
5986 |
+
$msg .= "\nCall Stack:\n" . $callStackMsg;
|
|
|
5987 |
}
|
5988 |
|
5989 |
+
return $msg;
|
5990 |
}
|
5991 |
|
5992 |
/**
|
5994 |
* @param array $ExpectedArgs
|
5995 |
* @param int $nbActual
|
5996 |
* @return CompilerException
|
5997 |
+
*
|
5998 |
+
* @deprecated
|
5999 |
*/
|
6000 |
public function errorArgsNumber($functionName, $ExpectedArgs, $nbActual)
|
6001 |
{
|
6002 |
+
@trigger_error(sprintf('The "%s" method is deprecated.', __METHOD__), E_USER_DEPRECATED);
|
6003 |
+
|
6004 |
$nbExpected = \count($ExpectedArgs);
|
6005 |
|
6006 |
if ($nbActual > $nbExpected) {
|
6029 |
/**
|
6030 |
* Beautify call stack for output
|
6031 |
*
|
6032 |
+
* @param boolean $all
|
6033 |
+
* @param int|null $limit
|
6034 |
*
|
6035 |
* @return string
|
6036 |
*/
|
6044 |
if ($all || (isset($call['n']) && $call['n'])) {
|
6045 |
$msg = '#' . $ncall++ . ' ' . $call['n'] . ' ';
|
6046 |
$msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]])
|
6047 |
+
? $this->getPrettyPath($this->sourceNames[$call[Parser::SOURCE_INDEX]])
|
6048 |
: '(unknown file)');
|
6049 |
$msg .= ' on line ' . $call[Parser::SOURCE_LINE];
|
6050 |
|
6076 |
|
6077 |
$file = $this->sourceNames[$env->block->sourceIndex];
|
6078 |
|
6079 |
+
if ($file === null) {
|
6080 |
+
continue;
|
6081 |
+
}
|
6082 |
+
|
6083 |
if (realpath($file) === $name) {
|
6084 |
throw $this->error('An @import loop has been found: %s imports %s', $file, basename($file));
|
6085 |
}
|
6092 |
* @param Object $func
|
6093 |
* @param array $argValues
|
6094 |
*
|
6095 |
+
* @return array|Number
|
6096 |
*/
|
6097 |
protected function callScssFunction($func, $argValues)
|
6098 |
{
|
6132 |
* Call built-in and registered (PHP) functions
|
6133 |
*
|
6134 |
* @param string $name
|
6135 |
+
* @param callable $function
|
6136 |
* @param array $prototype
|
6137 |
* @param array $args
|
6138 |
*
|
6139 |
+
* @return array|Number|null
|
6140 |
*/
|
6141 |
protected function callNativeFunction($name, $function, $prototype, $args)
|
6142 |
{
|
6148 |
}
|
6149 |
@list($sorted, $kwargs) = $sorted_kwargs;
|
6150 |
|
6151 |
+
if ($name !== 'if') {
|
|
|
|
|
|
|
|
|
|
|
|
|
6152 |
foreach ($sorted as &$val) {
|
6153 |
+
if ($val !== null) {
|
6154 |
+
$val = $this->reduce($val, true);
|
6155 |
+
}
|
6156 |
}
|
6157 |
}
|
6158 |
|
6162 |
return null;
|
6163 |
}
|
6164 |
|
6165 |
+
if (\is_array($returnValue) || $returnValue instanceof Number) {
|
6166 |
+
return $returnValue;
|
6167 |
+
}
|
6168 |
+
|
6169 |
+
@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);
|
6170 |
+
|
6171 |
+
return $this->coerceValue($returnValue);
|
6172 |
}
|
6173 |
|
6174 |
/**
|
6186 |
|
6187 |
/**
|
6188 |
* Normalize native function name
|
6189 |
+
*
|
6190 |
+
* @internal
|
6191 |
+
*
|
6192 |
+
* @param string $name
|
6193 |
+
*
|
6194 |
* @return string
|
6195 |
*/
|
6196 |
public static function normalizeNativeFunctionName($name)
|
6208 |
|
6209 |
/**
|
6210 |
* Check if a function is a native built-in scss function, for css parsing
|
6211 |
+
*
|
6212 |
+
* @internal
|
6213 |
+
*
|
6214 |
+
* @param string $name
|
6215 |
+
*
|
6216 |
* @return bool
|
6217 |
*/
|
6218 |
public static function isNativeFunction($name)
|
6224 |
* Sorts keyword arguments
|
6225 |
*
|
6226 |
* @param string $functionName
|
6227 |
+
* @param array|null $prototypes
|
6228 |
* @param array $args
|
6229 |
*
|
6230 |
* @return array|null
|
6260 |
// notation 100 127 255 / 0 is in fact a simple list of 4 values
|
6261 |
foreach ($args as $k => $arg) {
|
6262 |
if ($arg[1][0] === Type::T_LIST && \count($arg[1][2]) === 3) {
|
6263 |
+
$args[$k][1][2] = $this->extractSlashAlphaInColorFunction($arg[1][2]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6264 |
}
|
6265 |
}
|
6266 |
}
|
6267 |
|
6268 |
+
list($positionalArgs, $namedArgs, $names, $separator, $hasSplat) = $this->evaluateArguments($args, false);
|
6269 |
|
6270 |
if (! \is_array(reset($prototypes))) {
|
6271 |
$prototypes = [$prototypes];
|
6272 |
}
|
6273 |
|
6274 |
+
$parsedPrototypes = array_map([$this, 'parseFunctionPrototype'], $prototypes);
|
6275 |
+
assert(!empty($parsedPrototypes));
|
6276 |
+
$matchedPrototype = $this->selectFunctionPrototype($parsedPrototypes, \count($positionalArgs), $names);
|
6277 |
+
|
6278 |
+
$this->verifyPrototype($matchedPrototype, \count($positionalArgs), $names, $hasSplat);
|
6279 |
+
|
6280 |
+
$vars = $this->applyArgumentsToDeclaration($matchedPrototype, $positionalArgs, $namedArgs, $separator);
|
6281 |
+
|
6282 |
+
$finalArgs = [];
|
6283 |
$keyArgs = [];
|
6284 |
|
6285 |
+
foreach ($matchedPrototype['arguments'] as $argument) {
|
6286 |
+
list($normalizedName, $originalName, $default) = $argument;
|
|
|
6287 |
|
6288 |
+
if (isset($vars[$normalizedName])) {
|
6289 |
+
$value = $vars[$normalizedName];
|
6290 |
+
} else {
|
6291 |
+
$value = $default;
|
6292 |
+
}
|
6293 |
|
6294 |
+
// special null value as default: translate to real null here
|
6295 |
+
if ($value === [Type::T_KEYWORD, 'null']) {
|
6296 |
+
$value = null;
|
6297 |
+
}
|
6298 |
|
6299 |
+
$finalArgs[] = $value;
|
6300 |
+
$keyArgs[$originalName] = $value;
|
6301 |
+
}
|
6302 |
|
6303 |
+
if ($matchedPrototype['rest_argument'] !== null) {
|
6304 |
+
$value = $vars[$matchedPrototype['rest_argument']];
|
|
|
|
|
|
|
|
|
|
|
6305 |
|
6306 |
+
$finalArgs[] = $value;
|
6307 |
+
$keyArgs[$matchedPrototype['rest_argument']] = $value;
|
6308 |
+
}
|
6309 |
|
6310 |
+
return [$finalArgs, $keyArgs];
|
6311 |
+
}
|
6312 |
|
6313 |
+
/**
|
6314 |
+
* Parses a function prototype to the internal representation of arguments.
|
6315 |
+
*
|
6316 |
+
* The input is an array of strings describing each argument, as supported
|
6317 |
+
* in {@see registerFunction}. Argument names don't include the `$`.
|
6318 |
+
* The output contains the list of positional argument, with their normalized
|
6319 |
+
* name (underscores are replaced by dashes), their original name (to be used
|
6320 |
+
* in case of error reporting) and their default value. The output also contains
|
6321 |
+
* the normalized name of the rest argument, or null if the function prototype
|
6322 |
+
* is not variadic.
|
6323 |
+
*
|
6324 |
+
* @param string[] $prototype
|
6325 |
+
*
|
6326 |
+
* @return array
|
6327 |
+
* @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
|
6328 |
+
*/
|
6329 |
+
private function parseFunctionPrototype(array $prototype)
|
6330 |
+
{
|
6331 |
+
static $parser = null;
|
6332 |
+
|
6333 |
+
$arguments = [];
|
6334 |
+
$restArgument = null;
|
6335 |
|
6336 |
+
foreach ($prototype as $p) {
|
6337 |
+
if (null !== $restArgument) {
|
6338 |
+
throw new \InvalidArgumentException('The argument declaration is invalid. The rest argument must be the last one.');
|
6339 |
}
|
6340 |
|
6341 |
+
$default = null;
|
6342 |
+
$p = explode(':', $p, 2);
|
6343 |
+
$name = str_replace('_', '-', $p[0]);
|
6344 |
|
6345 |
+
if (isset($p[1])) {
|
6346 |
+
$defaultSource = trim($p[1]);
|
|
|
6347 |
|
6348 |
+
if ($defaultSource === 'null') {
|
6349 |
+
// differentiate this null from the static::$null
|
6350 |
+
$default = [Type::T_KEYWORD, 'null'];
|
6351 |
+
} else {
|
6352 |
+
if (\is_null($parser)) {
|
6353 |
+
$parser = $this->parserFactory(__METHOD__);
|
6354 |
}
|
6355 |
+
|
6356 |
+
$parser->parseValue($defaultSource, $default);
|
6357 |
}
|
6358 |
+
}
|
6359 |
|
6360 |
+
if (substr($name, -3) === '...') {
|
6361 |
+
$restArgument = substr($name, 0, -3);
|
6362 |
+
} else {
|
6363 |
+
$arguments[] = [$name, $p[0], $default];
|
6364 |
+
}
|
6365 |
+
}
|
6366 |
|
6367 |
+
return [
|
6368 |
+
'arguments' => $arguments,
|
6369 |
+
'rest_argument' => $restArgument,
|
6370 |
+
];
|
6371 |
+
}
|
6372 |
|
6373 |
+
/**
|
6374 |
+
* Returns the function prototype for the given positional and named arguments.
|
6375 |
+
*
|
6376 |
+
* If no exact match is found, finds the closest approximation. Note that this
|
6377 |
+
* doesn't guarantee that $positional and $names are valid for the returned
|
6378 |
+
* prototype.
|
6379 |
+
*
|
6380 |
+
* @param array[] $prototypes
|
6381 |
+
* @param int $positional
|
6382 |
+
* @param array<string, string> $names A set of names, as both keys and values
|
6383 |
+
*
|
6384 |
+
* @return array
|
6385 |
+
*
|
6386 |
+
* @phpstan-param non-empty-list<array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}> $prototypes
|
6387 |
+
* @phpstan-return array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null}
|
6388 |
+
*/
|
6389 |
+
private function selectFunctionPrototype(array $prototypes, $positional, array $names)
|
6390 |
+
{
|
6391 |
+
$fuzzyMatch = null;
|
6392 |
+
$minMismatchDistance = null;
|
6393 |
|
6394 |
+
foreach ($prototypes as $prototype) {
|
6395 |
+
// Ideally, find an exact match.
|
6396 |
+
if ($this->checkPrototypeMatches($prototype, $positional, $names)) {
|
6397 |
+
return $prototype;
|
6398 |
+
}
|
6399 |
|
6400 |
+
$mismatchDistance = \count($prototype['arguments']) - $positional;
|
|
|
|
|
6401 |
|
6402 |
+
if ($minMismatchDistance !== null) {
|
6403 |
+
if (abs($mismatchDistance) > abs($minMismatchDistance)) {
|
6404 |
+
continue;
|
6405 |
+
}
|
6406 |
|
6407 |
+
// If two overloads have the same mismatch distance, favor the overload
|
6408 |
+
// that has more arguments.
|
6409 |
+
if (abs($mismatchDistance) === abs($minMismatchDistance) && $mismatchDistance < 0) {
|
6410 |
+
continue;
|
6411 |
}
|
|
|
|
|
|
|
6412 |
}
|
6413 |
+
|
6414 |
+
$minMismatchDistance = $mismatchDistance;
|
6415 |
+
$fuzzyMatch = $prototype;
|
6416 |
}
|
6417 |
|
6418 |
+
return $fuzzyMatch;
|
6419 |
+
}
|
6420 |
+
|
6421 |
+
/**
|
6422 |
+
* Checks whether the argument invocation matches the callable prototype.
|
6423 |
+
*
|
6424 |
+
* The rules are similar to {@see verifyPrototype}. The boolean return value
|
6425 |
+
* avoids the overhead of building and catching exceptions when the reason of
|
6426 |
+
* not matching the prototype does not need to be known.
|
6427 |
+
*
|
6428 |
+
* @param array $prototype
|
6429 |
+
* @param int $positional
|
6430 |
+
* @param array<string, string> $names
|
6431 |
+
*
|
6432 |
+
* @return bool
|
6433 |
+
*
|
6434 |
+
* @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
|
6435 |
+
*/
|
6436 |
+
private function checkPrototypeMatches(array $prototype, $positional, array $names)
|
6437 |
+
{
|
6438 |
+
$nameUsed = 0;
|
6439 |
+
|
6440 |
+
foreach ($prototype['arguments'] as $i => $argument) {
|
6441 |
+
list ($name, $originalName, $default) = $argument;
|
6442 |
+
|
6443 |
+
if ($i < $positional) {
|
6444 |
+
if (isset($names[$name])) {
|
6445 |
+
return false;
|
6446 |
}
|
6447 |
+
} elseif (isset($names[$name])) {
|
6448 |
+
$nameUsed++;
|
6449 |
+
} elseif ($default === null) {
|
6450 |
+
return false;
|
6451 |
}
|
6452 |
+
}
|
6453 |
|
6454 |
+
if ($prototype['rest_argument'] !== null) {
|
6455 |
+
return true;
|
6456 |
}
|
6457 |
|
6458 |
+
if ($positional > \count($prototype['arguments'])) {
|
6459 |
+
return false;
|
6460 |
+
}
|
6461 |
+
|
6462 |
+
if ($nameUsed < \count($names)) {
|
6463 |
+
return false;
|
6464 |
+
}
|
6465 |
+
|
6466 |
+
return true;
|
6467 |
}
|
6468 |
|
6469 |
/**
|
6470 |
+
* Verifies that the argument invocation is valid for the callable prototype.
|
6471 |
*
|
6472 |
+
* @param array $prototype
|
6473 |
+
* @param int $positional
|
6474 |
+
* @param array<string, string> $names
|
6475 |
+
* @param bool $hasSplat
|
|
|
6476 |
*
|
6477 |
+
* @return void
|
6478 |
*
|
6479 |
+
* @throws SassScriptException
|
6480 |
+
*
|
6481 |
+
* @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
|
6482 |
*/
|
6483 |
+
private function verifyPrototype(array $prototype, $positional, array $names, $hasSplat)
|
6484 |
{
|
6485 |
+
$nameUsed = 0;
|
6486 |
|
6487 |
+
foreach ($prototype['arguments'] as $i => $argument) {
|
6488 |
+
list ($name, $originalName, $default) = $argument;
|
|
|
6489 |
|
6490 |
+
if ($i < $positional) {
|
6491 |
+
if (isset($names[$name])) {
|
6492 |
+
throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName));
|
6493 |
+
}
|
6494 |
+
} elseif (isset($names[$name])) {
|
6495 |
+
$nameUsed++;
|
6496 |
+
} elseif ($default === null) {
|
6497 |
+
throw new SassScriptException(sprintf('Missing argument $%s', $originalName));
|
6498 |
+
}
|
6499 |
+
}
|
6500 |
|
6501 |
+
if ($prototype['rest_argument'] !== null) {
|
6502 |
+
return;
|
6503 |
}
|
6504 |
|
6505 |
+
if ($positional > \count($prototype['arguments'])) {
|
6506 |
+
$message = sprintf(
|
6507 |
+
'Only %d %sargument%s allowed, but %d %s passed.',
|
6508 |
+
\count($prototype['arguments']),
|
6509 |
+
empty($names) ? '' : 'positional ',
|
6510 |
+
\count($prototype['arguments']) === 1 ? '' : 's',
|
6511 |
+
$positional,
|
6512 |
+
$positional === 1 ? 'was' : 'were'
|
6513 |
+
);
|
6514 |
+
if (!$hasSplat) {
|
6515 |
+
throw new SassScriptException($message);
|
6516 |
+
}
|
6517 |
|
6518 |
+
$message = $this->addLocationToMessage($message);
|
6519 |
+
$message .= "\nThis will be an error in future versions of Sass.";
|
6520 |
+
$this->logger->warn($message, true);
|
6521 |
+
}
|
6522 |
+
|
6523 |
+
if ($nameUsed < \count($names)) {
|
6524 |
+
$unknownNames = array_values(array_diff($names, array_column($prototype['arguments'], 0)));
|
6525 |
+
$lastName = array_pop($unknownNames);
|
6526 |
+
$message = sprintf(
|
6527 |
+
'No argument%s named $%s%s.',
|
6528 |
+
$unknownNames ? 's' : '',
|
6529 |
+
$unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
|
6530 |
+
$lastName
|
6531 |
+
);
|
6532 |
+
throw new SassScriptException($message);
|
6533 |
+
}
|
6534 |
+
}
|
6535 |
|
6536 |
+
/**
|
6537 |
+
* Evaluates the argument from the invocation.
|
6538 |
+
*
|
6539 |
+
* This returns several things about this invocation:
|
6540 |
+
* - the list of positional arguments
|
6541 |
+
* - the map of named arguments, indexed by normalized names
|
6542 |
+
* - the set of names used in the arguments (that's an array using the normalized names as keys for O(1) access)
|
6543 |
+
* - the separator used by the list using the splat operator, if any
|
6544 |
+
* - a boolean indicator whether any splat argument (list or map) was used, to support the incomplete error reporting.
|
6545 |
+
*
|
6546 |
+
* @param array[] $args
|
6547 |
+
* @param bool $reduce Whether arguments should be reduced to their value
|
6548 |
+
*
|
6549 |
+
* @return array
|
6550 |
+
*
|
6551 |
+
* @throws SassScriptException
|
6552 |
+
*
|
6553 |
+
* @phpstan-return array{0: list<array|Number>, 1: array<string, array|Number>, 2: array<string, string>, 3: string|null, 4: bool}
|
6554 |
+
*/
|
6555 |
+
private function evaluateArguments(array $args, $reduce = true)
|
6556 |
+
{
|
6557 |
+
// this represents trailing commas
|
6558 |
+
if (\count($args) && end($args) === static::$null) {
|
6559 |
+
array_pop($args);
|
6560 |
}
|
6561 |
|
6562 |
+
$splatSeparator = null;
|
6563 |
+
$keywordArgs = [];
|
6564 |
+
$names = [];
|
6565 |
+
$positionalArgs = [];
|
6566 |
+
$hasKeywordArgument = false;
|
6567 |
+
$hasSplat = false;
|
6568 |
|
6569 |
+
foreach ($args as $arg) {
|
6570 |
+
if (!empty($arg[0])) {
|
|
|
6571 |
$hasKeywordArgument = true;
|
6572 |
|
6573 |
+
assert(\is_string($arg[0][1]));
|
6574 |
+
$name = str_replace('_', '-', $arg[0][1]);
|
6575 |
|
6576 |
+
if (isset($keywordArgs[$name])) {
|
6577 |
+
throw new SassScriptException(sprintf('Duplicate named argument $%s.', $arg[0][1]));
|
|
|
|
|
|
|
|
|
|
|
6578 |
}
|
6579 |
|
6580 |
+
$keywordArgs[$name] = $this->maybeReduce($reduce, $arg[1]);
|
6581 |
+
$names[$name] = $name;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6582 |
} elseif (! empty($arg[2])) {
|
6583 |
// $arg[2] means a var followed by ... in the arg ($list... )
|
6584 |
$val = $this->reduce($arg[1], true);
|
6585 |
+
$hasSplat = true;
|
6586 |
|
6587 |
if ($val[0] === Type::T_LIST) {
|
6588 |
+
foreach ($val[2] as $item) {
|
6589 |
+
if (\is_null($splatSeparator)) {
|
6590 |
+
$splatSeparator = $val[1];
|
6591 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
6592 |
|
6593 |
+
$positionalArgs[] = $this->maybeReduce($reduce, $item);
|
6594 |
+
}
|
6595 |
+
|
6596 |
+
if (isset($val[3]) && \is_array($val[3])) {
|
6597 |
+
foreach ($val[3] as $name => $item) {
|
6598 |
+
assert(\is_string($name));
|
6599 |
+
|
6600 |
+
$normalizedName = str_replace('_', '-', $name);
|
6601 |
+
|
6602 |
+
if (isset($keywordArgs[$normalizedName])) {
|
6603 |
+
throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
|
6604 |
}
|
6605 |
|
6606 |
+
$keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
|
6607 |
+
$names[$normalizedName] = $normalizedName;
|
6608 |
+
$hasKeywordArgument = true;
|
6609 |
}
|
6610 |
}
|
6611 |
} elseif ($val[0] === Type::T_MAP) {
|
6614 |
$item = $val[2][$i];
|
6615 |
|
6616 |
if (! is_numeric($name)) {
|
6617 |
+
$normalizedName = str_replace('_', '-', $name);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6618 |
|
6619 |
+
if (isset($keywordArgs[$normalizedName])) {
|
6620 |
+
throw new SassScriptException(sprintf('Duplicate named argument $%s.', $name));
|
|
|
|
|
6621 |
}
|
6622 |
+
|
6623 |
+
$keywordArgs[$normalizedName] = $this->maybeReduce($reduce, $item);
|
6624 |
+
$names[$normalizedName] = $normalizedName;
|
6625 |
+
$hasKeywordArgument = true;
|
6626 |
} else {
|
6627 |
if (\is_null($splatSeparator)) {
|
6628 |
$splatSeparator = $val[1];
|
6629 |
}
|
6630 |
|
6631 |
+
$positionalArgs[] = $this->maybeReduce($reduce, $item);
|
6632 |
}
|
6633 |
}
|
6634 |
+
} elseif ($val[0] !== Type::T_NULL) { // values other than null are treated a single-element lists, while null is the empty list
|
6635 |
+
$positionalArgs[] = $this->maybeReduce($reduce, $val);
|
6636 |
}
|
6637 |
} elseif ($hasKeywordArgument) {
|
6638 |
+
throw new SassScriptException('Positional arguments must come before keyword arguments.');
|
6639 |
} else {
|
6640 |
+
$positionalArgs[] = $this->maybeReduce($reduce, $arg[1]);
|
6641 |
}
|
6642 |
}
|
6643 |
|
6644 |
+
return [$positionalArgs, $keywordArgs, $names, $splatSeparator, $hasSplat];
|
6645 |
+
}
|
6646 |
+
|
6647 |
+
/**
|
6648 |
+
* @param bool $reduce
|
6649 |
+
* @param array|Number $value
|
6650 |
+
*
|
6651 |
+
* @return array|Number
|
6652 |
+
*/
|
6653 |
+
private function maybeReduce($reduce, $value)
|
6654 |
+
{
|
6655 |
+
if ($reduce) {
|
6656 |
+
return $this->reduce($value, true);
|
6657 |
+
}
|
6658 |
+
|
6659 |
+
return $value;
|
6660 |
+
}
|
6661 |
+
|
6662 |
+
/**
|
6663 |
+
* Apply argument values per definition
|
6664 |
+
*
|
6665 |
+
* @param array[] $argDef
|
6666 |
+
* @param array|null $argValues
|
6667 |
+
* @param boolean $storeInEnv
|
6668 |
+
* @param boolean $reduce
|
6669 |
+
* only used if $storeInEnv = false
|
6670 |
+
*
|
6671 |
+
* @return array<string, array|Number>
|
6672 |
+
*
|
6673 |
+
* @phpstan-param list<array{0: string, 1: array|Number|null, 2: bool}> $argDef
|
6674 |
+
*
|
6675 |
+
* @throws \Exception
|
6676 |
+
*/
|
6677 |
+
protected function applyArguments($argDef, $argValues, $storeInEnv = true, $reduce = true)
|
6678 |
+
{
|
6679 |
+
$output = [];
|
6680 |
+
|
6681 |
+
if (\is_null($argValues)) {
|
6682 |
+
$argValues = [];
|
6683 |
+
}
|
6684 |
+
|
6685 |
+
if ($storeInEnv) {
|
6686 |
+
$storeEnv = $this->getStoreEnv();
|
6687 |
+
|
6688 |
+
$env = new Environment();
|
6689 |
+
$env->store = $storeEnv->store;
|
6690 |
+
}
|
6691 |
+
|
6692 |
+
$prototype = ['arguments' => [], 'rest_argument' => null];
|
6693 |
+
$originalRestArgumentName = null;
|
6694 |
+
|
6695 |
+
foreach ($argDef as $i => $arg) {
|
6696 |
+
list($name, $default, $isVariable) = $arg;
|
6697 |
+
$normalizedName = str_replace('_', '-', $name);
|
6698 |
|
6699 |
if ($isVariable) {
|
6700 |
+
$originalRestArgumentName = $name;
|
6701 |
+
$prototype['rest_argument'] = $normalizedName;
|
6702 |
+
} else {
|
6703 |
+
$prototype['arguments'][] = [$normalizedName, $name, !empty($default) ? $default : null];
|
6704 |
+
}
|
6705 |
+
}
|
|
|
6706 |
|
6707 |
+
list($positionalArgs, $namedArgs, $names, $splatSeparator, $hasSplat) = $this->evaluateArguments($argValues, $reduce);
|
6708 |
|
6709 |
+
$this->verifyPrototype($prototype, \count($positionalArgs), $names, $hasSplat);
|
|
|
|
|
6710 |
|
6711 |
+
$vars = $this->applyArgumentsToDeclaration($prototype, $positionalArgs, $namedArgs, $splatSeparator);
|
|
|
|
|
6712 |
|
6713 |
+
foreach ($prototype['arguments'] as $argument) {
|
6714 |
+
list($normalizedName, $name) = $argument;
|
6715 |
+
|
6716 |
+
if (!isset($vars[$normalizedName])) {
|
|
|
|
|
|
|
|
|
6717 |
continue;
|
6718 |
+
}
|
6719 |
+
|
6720 |
+
$val = $vars[$normalizedName];
|
6721 |
+
|
6722 |
+
if ($storeInEnv) {
|
6723 |
+
$this->set($name, $this->reduce($val, true), true, $env);
|
6724 |
} else {
|
6725 |
+
$output[$name] = ($reduce ? $this->reduce($val, true) : $val);
|
6726 |
}
|
6727 |
+
}
|
6728 |
+
|
6729 |
+
if ($prototype['rest_argument'] !== null) {
|
6730 |
+
assert($originalRestArgumentName !== null);
|
6731 |
+
$name = $originalRestArgumentName;
|
6732 |
+
$val = $vars[$prototype['rest_argument']];
|
6733 |
|
6734 |
if ($storeInEnv) {
|
6735 |
$this->set($name, $this->reduce($val, true), true, $env);
|
6742 |
$storeEnv->store = $env->store;
|
6743 |
}
|
6744 |
|
6745 |
+
foreach ($prototype['arguments'] as $argument) {
|
6746 |
+
list($normalizedName, $name, $default) = $argument;
|
6747 |
|
6748 |
+
if (isset($vars[$normalizedName])) {
|
6749 |
continue;
|
6750 |
}
|
6751 |
+
assert($default !== null);
|
6752 |
|
6753 |
if ($storeInEnv) {
|
6754 |
$this->set($name, $this->reduce($default, true), true);
|
6760 |
return $output;
|
6761 |
}
|
6762 |
|
6763 |
+
/**
|
6764 |
+
* Apply argument values per definition.
|
6765 |
+
*
|
6766 |
+
* This method assumes that the arguments are valid for the provided prototype.
|
6767 |
+
* The validation with {@see verifyPrototype} must have been run before calling
|
6768 |
+
* it.
|
6769 |
+
* Arguments are returned as a map from the normalized argument names to the
|
6770 |
+
* value. Additional arguments are collected in a sass argument list available
|
6771 |
+
* under the name of the rest argument in the result.
|
6772 |
+
*
|
6773 |
+
* Defaults are not applied as they are resolved in a different environment.
|
6774 |
+
*
|
6775 |
+
* @param array $prototype
|
6776 |
+
* @param array<array|Number> $positionalArgs
|
6777 |
+
* @param array<string, array|Number> $namedArgs
|
6778 |
+
* @param string|null $splatSeparator
|
6779 |
+
*
|
6780 |
+
* @return array<string, array|Number>
|
6781 |
+
*
|
6782 |
+
* @phpstan-param array{arguments: list<array{0: string, 1: string, 2: array|Number|null}>, rest_argument: string|null} $prototype
|
6783 |
+
*/
|
6784 |
+
private function applyArgumentsToDeclaration(array $prototype, array $positionalArgs, array $namedArgs, $splatSeparator)
|
6785 |
+
{
|
6786 |
+
$output = [];
|
6787 |
+
$minLength = min(\count($positionalArgs), \count($prototype['arguments']));
|
6788 |
+
|
6789 |
+
for ($i = 0; $i < $minLength; $i++) {
|
6790 |
+
list($name) = $prototype['arguments'][$i];
|
6791 |
+
$val = $positionalArgs[$i];
|
6792 |
+
|
6793 |
+
$output[$name] = $val;
|
6794 |
+
}
|
6795 |
+
|
6796 |
+
$restNamed = $namedArgs;
|
6797 |
+
|
6798 |
+
for ($i = \count($positionalArgs); $i < \count($prototype['arguments']); $i++) {
|
6799 |
+
$argument = $prototype['arguments'][$i];
|
6800 |
+
list($name) = $argument;
|
6801 |
+
|
6802 |
+
if (isset($namedArgs[$name])) {
|
6803 |
+
$val = $namedArgs[$name];
|
6804 |
+
unset($restNamed[$name]);
|
6805 |
+
} else {
|
6806 |
+
continue;
|
6807 |
+
}
|
6808 |
+
|
6809 |
+
$output[$name] = $val;
|
6810 |
+
}
|
6811 |
+
|
6812 |
+
if ($prototype['rest_argument'] !== null) {
|
6813 |
+
$name = $prototype['rest_argument'];
|
6814 |
+
$rest = array_values(array_slice($positionalArgs, \count($prototype['arguments'])));
|
6815 |
+
|
6816 |
+
$val = [Type::T_LIST, \is_null($splatSeparator) ? ',' : $splatSeparator , $rest, $restNamed];
|
6817 |
+
|
6818 |
+
$output[$name] = $val;
|
6819 |
+
}
|
6820 |
+
|
6821 |
+
return $output;
|
6822 |
+
}
|
6823 |
+
|
6824 |
/**
|
6825 |
* Coerce a php value into a scss one
|
6826 |
*
|
6827 |
* @param mixed $value
|
6828 |
*
|
6829 |
+
* @return array|Number
|
6830 |
*/
|
6831 |
protected function coerceValue($value)
|
6832 |
{
|
6833 |
+
if (\is_array($value) || $value instanceof Number) {
|
6834 |
return $value;
|
6835 |
}
|
6836 |
|
6843 |
}
|
6844 |
|
6845 |
if (is_numeric($value)) {
|
6846 |
+
return new Number($value, '');
|
6847 |
}
|
6848 |
|
6849 |
if ($value === '') {
|
6863 |
/**
|
6864 |
* Coerce something to map
|
6865 |
*
|
6866 |
+
* @param array|Number $item
|
6867 |
*
|
6868 |
+
* @return array|Number
|
6869 |
*/
|
6870 |
protected function coerceMap($item)
|
6871 |
{
|
6874 |
}
|
6875 |
|
6876 |
if (
|
6877 |
+
$item[0] === Type::T_LIST &&
|
6878 |
+
$item[2] === []
|
|
|
6879 |
) {
|
6880 |
return static::$emptyMap;
|
6881 |
}
|
6886 |
/**
|
6887 |
* Coerce something to list
|
6888 |
*
|
6889 |
+
* @param array|Number $item
|
6890 |
+
* @param string $delim
|
6891 |
+
* @param boolean $removeTrailingNull
|
6892 |
*
|
6893 |
* @return array
|
6894 |
*/
|
6895 |
protected function coerceList($item, $delim = ',', $removeTrailingNull = false)
|
6896 |
{
|
6897 |
+
if ($item instanceof Number) {
|
6898 |
+
return [Type::T_LIST, $delim, [$item]];
|
6899 |
+
}
|
6900 |
+
|
6901 |
+
if ($item[0] === Type::T_LIST) {
|
6902 |
// remove trailing null from the list
|
6903 |
if ($removeTrailingNull && end($item[2]) === static::$null) {
|
6904 |
array_pop($item[2]);
|
6907 |
return $item;
|
6908 |
}
|
6909 |
|
6910 |
+
if ($item[0] === Type::T_MAP) {
|
6911 |
$keys = $item[1];
|
6912 |
$values = $item[2];
|
6913 |
$list = [];
|
6938 |
return [Type::T_LIST, ',', $list];
|
6939 |
}
|
6940 |
|
6941 |
+
return [Type::T_LIST, $delim, [$item]];
|
6942 |
}
|
6943 |
|
6944 |
/**
|
6945 |
* Coerce color for expression
|
6946 |
*
|
6947 |
+
* @param array|Number $value
|
6948 |
*
|
6949 |
+
* @return array|Number
|
6950 |
*/
|
6951 |
protected function coerceForExpression($value)
|
6952 |
{
|
6960 |
/**
|
6961 |
* Coerce value to color
|
6962 |
*
|
6963 |
+
* @param array|Number $value
|
6964 |
+
* @param bool $inRGBFunction
|
6965 |
*
|
6966 |
* @return array|null
|
6967 |
*/
|
6968 |
protected function coerceColor($value, $inRGBFunction = false)
|
6969 |
{
|
6970 |
+
if ($value instanceof Number) {
|
6971 |
+
return null;
|
6972 |
+
}
|
6973 |
+
|
6974 |
switch ($value[0]) {
|
6975 |
case Type::T_COLOR:
|
6976 |
for ($i = 1; $i <= 3; $i++) {
|
7056 |
if ($color[3] === 255) {
|
7057 |
$color[3] = 1; // fully opaque
|
7058 |
} else {
|
7059 |
+
$color[3] = round($color[3] / 255, Number::PRECISION);
|
7060 |
}
|
7061 |
}
|
7062 |
|
7079 |
}
|
7080 |
|
7081 |
/**
|
7082 |
+
* @param integer|Number $value
|
7083 |
+
* @param boolean $isAlpha
|
7084 |
*
|
7085 |
* @return integer|mixed
|
7086 |
*/
|
7098 |
* @param integer|float $min
|
7099 |
* @param integer|float $max
|
7100 |
* @param boolean $isInt
|
|
|
|
|
7101 |
*
|
7102 |
* @return integer|mixed
|
7103 |
*/
|
7104 |
+
protected function compileColorPartValue($value, $min, $max, $isInt = true)
|
7105 |
{
|
7106 |
if (! is_numeric($value)) {
|
7107 |
if (\is_array($value)) {
|
7108 |
$reduced = $this->reduce($value);
|
7109 |
|
7110 |
+
if ($reduced instanceof Number) {
|
7111 |
$value = $reduced;
|
7112 |
}
|
7113 |
}
|
7114 |
|
7115 |
+
if ($value instanceof Number) {
|
7116 |
+
if ($value->unitless()) {
|
7117 |
+
$num = $value->getDimension();
|
7118 |
+
} elseif ($value->hasUnit('%')) {
|
7119 |
+
$num = $max * $value->getDimension() / 100;
|
7120 |
+
} else {
|
7121 |
+
throw $this->error('Expected %s to have no units or "%%".', $value);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7122 |
}
|
7123 |
|
7124 |
$value = $num;
|
7132 |
$value = round($value);
|
7133 |
}
|
7134 |
|
7135 |
+
$value = min($max, max($min, $value));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7136 |
|
7137 |
return $value;
|
7138 |
}
|
7143 |
/**
|
7144 |
* Coerce value to string
|
7145 |
*
|
7146 |
+
* @param array|Number $value
|
7147 |
*
|
7148 |
+
* @return array
|
7149 |
*/
|
7150 |
protected function coerceString($value)
|
7151 |
{
|
7157 |
}
|
7158 |
|
7159 |
/**
|
7160 |
+
* Assert value is a string
|
7161 |
+
*
|
7162 |
+
* This method deals with internal implementation details of the value
|
7163 |
+
* representation where unquoted strings can sometimes be stored under
|
7164 |
+
* other types.
|
7165 |
+
* The returned value is always using the T_STRING type.
|
7166 |
*
|
7167 |
* @api
|
7168 |
*
|
7169 |
+
* @param array|Number $value
|
7170 |
+
* @param string|null $varName
|
7171 |
*
|
7172 |
* @return array
|
7173 |
*
|
7174 |
+
* @throws SassScriptException
|
7175 |
*/
|
7176 |
public function assertString($value, $varName = null)
|
7177 |
{
|
7182 |
|
7183 |
if (! \in_array($value[0], [Type::T_STRING, Type::T_KEYWORD])) {
|
7184 |
$value = $this->compileValue($value);
|
7185 |
+
throw SassScriptException::forArgument("$value is not a string.", $varName);
|
|
|
7186 |
}
|
7187 |
|
7188 |
+
return $this->coerceString($value);
|
|
|
|
|
7189 |
}
|
7190 |
|
7191 |
/**
|
7192 |
* Coerce value to a percentage
|
7193 |
*
|
7194 |
+
* @param array|Number $value
|
7195 |
*
|
7196 |
* @return integer|float
|
7197 |
*/
|
7198 |
protected function coercePercent($value)
|
7199 |
{
|
7200 |
+
if ($value instanceof Number) {
|
7201 |
+
if ($value->hasUnit('%')) {
|
7202 |
+
return $value->getDimension() / 100;
|
7203 |
}
|
7204 |
|
7205 |
+
return $value->getDimension();
|
7206 |
}
|
7207 |
|
7208 |
return 0;
|
7213 |
*
|
7214 |
* @api
|
7215 |
*
|
7216 |
+
* @param array|Number $value
|
7217 |
+
* @param string|null $varName
|
7218 |
*
|
7219 |
* @return array
|
7220 |
*
|
7221 |
+
* @throws SassScriptException
|
7222 |
*/
|
7223 |
+
public function assertMap($value, $varName = null)
|
7224 |
{
|
7225 |
$value = $this->coerceMap($value);
|
7226 |
|
7227 |
if ($value[0] !== Type::T_MAP) {
|
7228 |
+
$value = $this->compileValue($value);
|
7229 |
+
|
7230 |
+
throw SassScriptException::forArgument("$value is not a map.", $varName);
|
7231 |
}
|
7232 |
|
7233 |
return $value;
|
7238 |
*
|
7239 |
* @api
|
7240 |
*
|
7241 |
+
* @param array|Number $value
|
7242 |
*
|
7243 |
* @return array
|
7244 |
*
|
7253 |
return $value;
|
7254 |
}
|
7255 |
|
7256 |
+
/**
|
7257 |
+
* Gets the keywords of an argument list.
|
7258 |
+
*
|
7259 |
+
* Keys in the returned array are normalized names (underscores are replaced with dashes)
|
7260 |
+
* without the leading `$`.
|
7261 |
+
* Calling this helper with anything that an argument list received for a rest argument
|
7262 |
+
* of the function argument declaration is not supported.
|
7263 |
+
*
|
7264 |
+
* @param array|Number $value
|
7265 |
+
*
|
7266 |
+
* @return array<string, array|Number>
|
7267 |
+
*/
|
7268 |
+
public function getArgumentListKeywords($value)
|
7269 |
+
{
|
7270 |
+
if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
|
7271 |
+
throw new \InvalidArgumentException('The argument is not a sass argument list.');
|
7272 |
+
}
|
7273 |
+
|
7274 |
+
return $value[3];
|
7275 |
+
}
|
7276 |
+
|
7277 |
/**
|
7278 |
* Assert value is a color
|
7279 |
*
|
7280 |
* @api
|
7281 |
*
|
7282 |
+
* @param array|Number $value
|
7283 |
+
* @param string|null $varName
|
7284 |
*
|
7285 |
* @return array
|
7286 |
*
|
7287 |
+
* @throws SassScriptException
|
7288 |
*/
|
7289 |
+
public function assertColor($value, $varName = null)
|
7290 |
{
|
7291 |
if ($color = $this->coerceColor($value)) {
|
7292 |
return $color;
|
7293 |
}
|
7294 |
|
7295 |
+
$value = $this->compileValue($value);
|
7296 |
+
|
7297 |
+
throw SassScriptException::forArgument("$value is not a color.", $varName);
|
7298 |
}
|
7299 |
|
7300 |
/**
|
7302 |
*
|
7303 |
* @api
|
7304 |
*
|
7305 |
+
* @param array|Number $value
|
7306 |
+
* @param string|null $varName
|
7307 |
*
|
7308 |
+
* @return Number
|
7309 |
*
|
7310 |
+
* @throws SassScriptException
|
7311 |
*/
|
7312 |
public function assertNumber($value, $varName = null)
|
7313 |
{
|
7314 |
+
if (!$value instanceof Number) {
|
7315 |
$value = $this->compileValue($value);
|
7316 |
+
throw SassScriptException::forArgument("$value is not a number.", $varName);
|
|
|
7317 |
}
|
7318 |
|
7319 |
+
return $value;
|
7320 |
}
|
7321 |
|
7322 |
/**
|
7324 |
*
|
7325 |
* @api
|
7326 |
*
|
7327 |
+
* @param array|Number $value
|
7328 |
+
* @param string|null $varName
|
7329 |
*
|
7330 |
+
* @return integer
|
7331 |
*
|
7332 |
+
* @throws SassScriptException
|
7333 |
*/
|
7334 |
public function assertInteger($value, $varName = null)
|
7335 |
{
|
7336 |
+
$value = $this->assertNumber($value, $varName)->getDimension();
|
7337 |
+
if (round($value - \intval($value), Number::PRECISION) > 0) {
|
7338 |
+
throw SassScriptException::forArgument("$value is not an integer.", $varName);
|
|
|
|
|
7339 |
}
|
7340 |
|
7341 |
return intval($value);
|
7342 |
}
|
7343 |
|
7344 |
+
/**
|
7345 |
+
* Extract the ... / alpha on the last argument of channel arg
|
7346 |
+
* in color functions
|
7347 |
+
*
|
7348 |
+
* @param array $args
|
7349 |
+
* @return array
|
7350 |
+
*/
|
7351 |
+
private function extractSlashAlphaInColorFunction($args)
|
7352 |
+
{
|
7353 |
+
$last = end($args);
|
7354 |
+
if (\count($args) === 3 && $last[0] === Type::T_EXPRESSION && $last[1] === '/') {
|
7355 |
+
array_pop($args);
|
7356 |
+
$args[] = $last[2];
|
7357 |
+
$args[] = $last[3];
|
7358 |
+
}
|
7359 |
+
return $args;
|
7360 |
+
}
|
7361 |
+
|
7362 |
|
7363 |
/**
|
7364 |
* Make sure a color's components don't go out of bounds
|
7377 |
if ($c[$i] > 255) {
|
7378 |
$c[$i] = 255;
|
7379 |
}
|
7380 |
+
|
7381 |
+
if (!\is_int($c[$i])) {
|
7382 |
+
$c[$i] = round($c[$i]);
|
7383 |
+
}
|
7384 |
}
|
7385 |
|
7386 |
return $c;
|
7389 |
/**
|
7390 |
* Convert RGB to HSL
|
7391 |
*
|
7392 |
+
* @internal
|
7393 |
*
|
7394 |
* @param integer $red
|
7395 |
* @param integer $green
|
7461 |
/**
|
7462 |
* Convert HSL to RGB
|
7463 |
*
|
7464 |
+
* @internal
|
7465 |
*
|
7466 |
+
* @param int|float $hue H from 0 to 360
|
7467 |
+
* @param int|float $saturation S from 0 to 100
|
7468 |
+
* @param int|float $lightness L from 0 to 100
|
7469 |
*
|
7470 |
* @return array
|
7471 |
*/
|
7491 |
return $out;
|
7492 |
}
|
7493 |
|
7494 |
+
/**
|
7495 |
+
* Convert HWB to RGB
|
7496 |
+
* https://www.w3.org/TR/css-color-4/#hwb-to-rgb
|
7497 |
+
*
|
7498 |
+
* @api
|
7499 |
+
*
|
7500 |
+
* @param integer $hue H from 0 to 360
|
7501 |
+
* @param integer $whiteness W from 0 to 100
|
7502 |
+
* @param integer $blackness B from 0 to 100
|
7503 |
+
*
|
7504 |
+
* @return array
|
7505 |
+
*/
|
7506 |
+
private function HWBtoRGB($hue, $whiteness, $blackness)
|
7507 |
+
{
|
7508 |
+
$w = min(100, max(0, $whiteness)) / 100;
|
7509 |
+
$b = min(100, max(0, $blackness)) / 100;
|
7510 |
+
|
7511 |
+
$sum = $w + $b;
|
7512 |
+
if ($sum > 1.0) {
|
7513 |
+
$w = $w / $sum;
|
7514 |
+
$b = $b / $sum;
|
7515 |
+
}
|
7516 |
+
$b = min(1.0 - $w, $b);
|
7517 |
+
|
7518 |
+
$rgb = $this->toRGB($hue, 100, 50);
|
7519 |
+
for($i = 1; $i < 4; $i++) {
|
7520 |
+
$rgb[$i] *= (1.0 - $w - $b);
|
7521 |
+
$rgb[$i] = round($rgb[$i] + 255 * $w + 0.0001);
|
7522 |
+
}
|
7523 |
+
|
7524 |
+
return $rgb;
|
7525 |
+
}
|
7526 |
+
|
7527 |
+
/**
|
7528 |
+
* Convert RGB to HWB
|
7529 |
+
*
|
7530 |
+
* @api
|
7531 |
+
*
|
7532 |
+
* @param integer $red
|
7533 |
+
* @param integer $green
|
7534 |
+
* @param integer $blue
|
7535 |
+
*
|
7536 |
+
* @return array
|
7537 |
+
*/
|
7538 |
+
private function RGBtoHWB($red, $green, $blue)
|
7539 |
+
{
|
7540 |
+
$min = min($red, $green, $blue);
|
7541 |
+
$max = max($red, $green, $blue);
|
7542 |
+
|
7543 |
+
$d = $max - $min;
|
7544 |
+
|
7545 |
+
if ((int) $d === 0) {
|
7546 |
+
$h = 0;
|
7547 |
+
} else {
|
7548 |
+
|
7549 |
+
if ($red == $max) {
|
7550 |
+
$h = 60 * ($green - $blue) / $d;
|
7551 |
+
} elseif ($green == $max) {
|
7552 |
+
$h = 60 * ($blue - $red) / $d + 120;
|
7553 |
+
} elseif ($blue == $max) {
|
7554 |
+
$h = 60 * ($red - $green) / $d + 240;
|
7555 |
+
}
|
7556 |
+
}
|
7557 |
+
|
7558 |
+
return [Type::T_HWB, fmod($h, 360), $min / 255 * 100, 100 - $max / 255 *100];
|
7559 |
+
}
|
7560 |
+
|
7561 |
+
|
7562 |
// Built in functions
|
7563 |
|
7564 |
protected static $libCall = ['function', 'args...'];
|
7565 |
+
protected function libCall($args)
|
7566 |
{
|
7567 |
+
$functionReference = $args[0];
|
7568 |
|
7569 |
if (in_array($functionReference[0], [Type::T_STRING, Type::T_KEYWORD])) {
|
7570 |
+
$name = $this->compileStringContent($this->coerceString($functionReference));
|
7571 |
+
$warning = "Passing a string to call() is deprecated and will be illegal\n"
|
7572 |
. "in Sass 4.0. Use call(function-reference($name)) instead.";
|
7573 |
+
Warn::deprecation($warning);
|
7574 |
+
$functionReference = $this->libGetFunction([$this->assertString($functionReference, 'function')]);
|
7575 |
}
|
7576 |
|
7577 |
if ($functionReference === static::$null) {
|
7582 |
throw $this->error('Function reference expected, got ' . $functionReference[0]);
|
7583 |
}
|
7584 |
|
7585 |
+
$callArgs = [
|
7586 |
+
[null, $args[1], true]
|
7587 |
+
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7588 |
|
7589 |
return $this->reduce([Type::T_FUNCTION_CALL, $functionReference, $callArgs]);
|
7590 |
}
|
7596 |
];
|
7597 |
protected function libGetFunction($args)
|
7598 |
{
|
7599 |
+
$name = $this->compileStringContent($this->assertString(array_shift($args), 'name'));
|
7600 |
$isCss = false;
|
7601 |
|
7602 |
if (count($args)) {
|
7603 |
+
$isCss = array_shift($args);
|
7604 |
$isCss = (($isCss === static::$true) ? true : false);
|
7605 |
}
|
7606 |
|
7641 |
return static::$null;
|
7642 |
}
|
7643 |
|
7644 |
+
// Numbers are represented with value objects, for which the PHP equality operator does not
|
7645 |
+
// match the Sass rules (and we cannot overload it). As they are the only type of values
|
7646 |
+
// represented with a value object for now, they require a special case.
|
7647 |
+
if ($value instanceof Number) {
|
7648 |
+
$key = 0;
|
7649 |
+
foreach ($list[2] as $item) {
|
7650 |
+
$key++;
|
7651 |
+
$itemValue = $this->normalizeValue($item);
|
7652 |
+
|
7653 |
+
if ($itemValue instanceof Number && $value->equals($itemValue)) {
|
7654 |
+
return new Number($key, '');
|
7655 |
+
}
|
7656 |
+
}
|
7657 |
+
return static::$null;
|
7658 |
+
}
|
7659 |
+
|
7660 |
$values = [];
|
7661 |
|
7662 |
+
|
7663 |
foreach ($list[2] as $item) {
|
7664 |
$values[] = $this->normalizeValue($item);
|
7665 |
}
|
7666 |
|
7667 |
$key = array_search($this->normalizeValue($value), $values);
|
7668 |
|
7669 |
+
return false === $key ? static::$null : new Number($key + 1, '');
|
7670 |
}
|
7671 |
|
7672 |
protected static $libRgb = [
|
7733 |
return $this->libRgb($args, $kwargs, 'rgba');
|
7734 |
}
|
7735 |
|
7736 |
+
/**
|
7737 |
+
* Helper function for adjust_color, change_color, and scale_color
|
7738 |
+
*
|
7739 |
+
* @param array<array|Number> $args
|
7740 |
+
* @param string $operation
|
7741 |
+
* @param callable $fn
|
7742 |
+
*
|
7743 |
+
* @return array
|
7744 |
+
*
|
7745 |
+
* @phpstan-param callable(float|int, float|int|null, float|int): (float|int) $fn
|
7746 |
+
*/
|
7747 |
+
protected function alterColor(array $args, $operation, $fn)
|
7748 |
+
{
|
7749 |
+
$color = $this->assertColor($args[0], 'color');
|
7750 |
+
|
7751 |
+
if ($args[1][2]) {
|
7752 |
+
throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.');
|
7753 |
+
}
|
7754 |
+
|
7755 |
+
$kwargs = $this->getArgumentListKeywords($args[1]);
|
7756 |
+
|
7757 |
+
$scale = $operation === 'scale';
|
7758 |
+
$change = $operation === 'change';
|
7759 |
+
|
7760 |
+
/**
|
7761 |
+
* @param string $name
|
7762 |
+
* @param float|int $max
|
7763 |
+
* @param bool $checkPercent
|
7764 |
+
* @param bool $assertPercent
|
7765 |
+
*
|
7766 |
+
* @return float|int|null
|
7767 |
+
*/
|
7768 |
+
$getParam = function ($name, $max, $checkPercent = false, $assertPercent = false) use (&$kwargs, $scale, $change) {
|
7769 |
+
if (!isset($kwargs[$name])) {
|
7770 |
+
return null;
|
7771 |
+
}
|
7772 |
+
|
7773 |
+
$number = $this->assertNumber($kwargs[$name], $name);
|
7774 |
+
unset($kwargs[$name]);
|
7775 |
+
|
7776 |
+
if (!$scale && $checkPercent) {
|
7777 |
+
if (!$number->hasUnit('%')) {
|
7778 |
+
$warning = $this->error("{$name} Passing a number `$number` without unit % is deprecated.");
|
7779 |
+
$this->logger->warn($warning->getMessage(), true);
|
7780 |
+
}
|
7781 |
+
}
|
7782 |
+
|
7783 |
+
if ($scale || $assertPercent) {
|
7784 |
+
$number->assertUnit('%', $name);
|
7785 |
+
}
|
7786 |
+
|
7787 |
+
if ($scale) {
|
7788 |
+
$max = 100;
|
7789 |
+
}
|
7790 |
+
|
7791 |
+
return $number->valueInRange($change ? 0 : -$max, $max, $name);
|
7792 |
+
};
|
7793 |
+
|
7794 |
+
$alpha = $getParam('alpha', 1);
|
7795 |
+
$red = $getParam('red', 255);
|
7796 |
+
$green = $getParam('green', 255);
|
7797 |
+
$blue = $getParam('blue', 255);
|
7798 |
|
7799 |
+
if ($scale || !isset($kwargs['hue'])) {
|
7800 |
+
$hue = null;
|
7801 |
+
} else {
|
7802 |
+
$hueNumber = $this->assertNumber($kwargs['hue'], 'hue');
|
7803 |
+
unset($kwargs['hue']);
|
7804 |
+
$hue = $hueNumber->getDimension();
|
7805 |
+
}
|
7806 |
+
$saturation = $getParam('saturation', 100, true);
|
7807 |
+
$lightness = $getParam('lightness', 100, true);
|
7808 |
+
$whiteness = $getParam('whiteness', 100, false, true);
|
7809 |
+
$blackness = $getParam('blackness', 100, false, true);
|
7810 |
+
|
7811 |
+
if (!empty($kwargs)) {
|
7812 |
+
$unknownNames = array_keys($kwargs);
|
7813 |
+
$lastName = array_pop($unknownNames);
|
7814 |
+
$message = sprintf(
|
7815 |
+
'No argument%s named $%s%s.',
|
7816 |
+
$unknownNames ? 's' : '',
|
7817 |
+
$unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
|
7818 |
+
$lastName
|
7819 |
+
);
|
7820 |
+
throw new SassScriptException($message);
|
7821 |
+
}
|
7822 |
|
7823 |
+
$hasRgb = $red !== null || $green !== null || $blue !== null;
|
7824 |
+
$hasSL = $saturation !== null || $lightness !== null;
|
7825 |
+
$hasWB = $whiteness !== null || $blackness !== null;
|
7826 |
+
$found = false;
|
7827 |
|
7828 |
+
if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) {
|
7829 |
+
throw new SassScriptException(sprintf('RGB parameters may not be passed along with %s parameters.', $hasWB ? 'HWB' : 'HSL'));
|
7830 |
}
|
7831 |
|
7832 |
+
if ($hasWB && $hasSL) {
|
7833 |
+
throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.');
|
7834 |
+
}
|
7835 |
+
|
7836 |
+
if ($hasRgb) {
|
7837 |
+
$color[1] = round($fn($color[1], $red, 255));
|
7838 |
+
$color[2] = round($fn($color[2], $green, 255));
|
7839 |
+
$color[3] = round($fn($color[3], $blue, 255));
|
7840 |
+
} elseif ($hasWB) {
|
7841 |
+
$hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
|
7842 |
+
if ($hue !== null) {
|
7843 |
+
$hwb[1] = $change ? $hue : $hwb[1] + $hue;
|
7844 |
+
}
|
7845 |
+
$hwb[2] = $fn($hwb[2], $whiteness, 100);
|
7846 |
+
$hwb[3] = $fn($hwb[3], $blackness, 100);
|
7847 |
+
|
7848 |
+
$rgb = $this->HWBtoRGB($hwb[1], $hwb[2], $hwb[3]);
|
7849 |
+
|
7850 |
+
if (isset($color[4])) {
|
7851 |
+
$rgb[4] = $color[4];
|
7852 |
+
}
|
7853 |
+
|
7854 |
+
$color = $rgb;
|
7855 |
+
} elseif ($hue !== null || $hasSL) {
|
7856 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
7857 |
|
7858 |
+
if ($hue !== null) {
|
7859 |
+
$hsl[1] = $change ? $hue : $hsl[1] + $hue;
|
|
|
|
|
|
|
7860 |
}
|
7861 |
+
$hsl[2] = $fn($hsl[2], $saturation, 100);
|
7862 |
+
$hsl[3] = $fn($hsl[3], $lightness, 100);
|
7863 |
|
7864 |
$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
|
7865 |
|
7870 |
$color = $rgb;
|
7871 |
}
|
7872 |
|
7873 |
+
if ($alpha !== null) {
|
7874 |
+
$existingAlpha = isset($color[4]) ? $color[4] : 1;
|
7875 |
+
$color[4] = $fn($existingAlpha, $alpha, 1);
|
7876 |
+
}
|
7877 |
+
|
7878 |
return $color;
|
7879 |
}
|
7880 |
|
7881 |
+
protected static $libAdjustColor = ['color', 'kwargs...'];
|
|
|
|
|
|
|
7882 |
protected function libAdjustColor($args)
|
7883 |
{
|
7884 |
+
return $this->alterColor($args, 'adjust', function ($base, $alter, $max) {
|
7885 |
+
if ($alter === null) {
|
7886 |
+
return $base;
|
7887 |
+
}
|
7888 |
+
|
7889 |
+
$new = $base + $alter;
|
7890 |
+
|
7891 |
+
if ($new < 0) {
|
7892 |
+
return 0;
|
7893 |
+
}
|
7894 |
+
|
7895 |
+
if ($new > $max) {
|
7896 |
+
return $max;
|
7897 |
+
}
|
7898 |
+
|
7899 |
+
return $new;
|
7900 |
});
|
7901 |
}
|
7902 |
|
7903 |
+
protected static $libChangeColor = ['color', 'kwargs...'];
|
|
|
|
|
|
|
7904 |
protected function libChangeColor($args)
|
7905 |
{
|
7906 |
+
return $this->alterColor($args,'change', function ($base, $alter, $max) {
|
7907 |
+
if ($alter === null) {
|
7908 |
+
return $base;
|
7909 |
+
}
|
7910 |
+
|
7911 |
return $alter;
|
7912 |
});
|
7913 |
}
|
7914 |
|
7915 |
+
protected static $libScaleColor = ['color', 'kwargs...'];
|
|
|
|
|
|
|
7916 |
protected function libScaleColor($args)
|
7917 |
{
|
7918 |
+
return $this->alterColor($args, 'scale', function ($base, $scale, $max) {
|
7919 |
+
if ($scale === null) {
|
7920 |
+
return $base;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7921 |
}
|
7922 |
|
7923 |
$scale = $scale / 100;
|
7936 |
$color = $this->coerceColor($args[0]);
|
7937 |
|
7938 |
if (\is_null($color)) {
|
7939 |
+
throw $this->error('Error: argument `$color` of `ie-hex-str($color)` must be a color');
|
7940 |
}
|
7941 |
|
7942 |
$color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
|
7950 |
$color = $this->coerceColor($args[0]);
|
7951 |
|
7952 |
if (\is_null($color)) {
|
7953 |
+
throw $this->error('Error: argument `$color` of `red($color)` must be a color');
|
7954 |
}
|
7955 |
|
7956 |
+
return new Number((int) $color[1], '');
|
7957 |
}
|
7958 |
|
7959 |
protected static $libGreen = ['color'];
|
7962 |
$color = $this->coerceColor($args[0]);
|
7963 |
|
7964 |
if (\is_null($color)) {
|
7965 |
+
throw $this->error('Error: argument `$color` of `green($color)` must be a color');
|
7966 |
}
|
7967 |
|
7968 |
+
return new Number((int) $color[2], '');
|
7969 |
}
|
7970 |
|
7971 |
protected static $libBlue = ['color'];
|
7974 |
$color = $this->coerceColor($args[0]);
|
7975 |
|
7976 |
if (\is_null($color)) {
|
7977 |
+
throw $this->error('Error: argument `$color` of `blue($color)` must be a color');
|
7978 |
}
|
7979 |
|
7980 |
+
return new Number((int) $color[3], '');
|
7981 |
}
|
7982 |
|
7983 |
protected static $libAlpha = ['color'];
|
7984 |
protected function libAlpha($args)
|
7985 |
{
|
7986 |
if ($color = $this->coerceColor($args[0])) {
|
7987 |
+
return new Number(isset($color[4]) ? $color[4] : 1, '');
|
7988 |
}
|
7989 |
|
7990 |
// this might be the IE function, so return value unchanged
|
7996 |
{
|
7997 |
$value = $args[0];
|
7998 |
|
7999 |
+
if ($value instanceof Number) {
|
8000 |
return null;
|
8001 |
}
|
8002 |
|
8012 |
{
|
8013 |
list($first, $second, $weight) = $args;
|
8014 |
|
8015 |
+
$first = $this->assertColor($first, 'color1');
|
8016 |
+
$second = $this->assertColor($second, 'color2');
|
8017 |
+
$weight = $this->coercePercent($this->assertNumber($weight, 'weight'));
|
|
|
|
|
|
|
|
|
|
|
8018 |
|
8019 |
$firstAlpha = isset($first[4]) ? $first[4] : 1;
|
8020 |
$secondAlpha = isset($second[4]) ? $second[4] : 1;
|
8040 |
|
8041 |
protected static $libHsl = [
|
8042 |
['channels'],
|
8043 |
+
['hue', 'saturation'],
|
8044 |
['hue', 'saturation', 'lightness'],
|
8045 |
['hue', 'saturation', 'lightness', 'alpha'] ];
|
8046 |
protected function libHsl($args, $kwargs, $funcName = 'hsl')
|
8056 |
$args_to_check = $kwargs['channels'][2];
|
8057 |
}
|
8058 |
|
8059 |
+
if (\count($args) === 2) {
|
8060 |
+
// if var() is used as an argument, return as a css function
|
8061 |
+
foreach ($args as $arg) {
|
8062 |
+
if ($arg[0] === Type::T_FUNCTION && in_array($arg[1], ['var'])) {
|
8063 |
+
return null;
|
8064 |
+
}
|
8065 |
+
}
|
8066 |
+
|
8067 |
+
throw new SassScriptException('Missing argument $lightness.');
|
8068 |
+
}
|
8069 |
|
8070 |
foreach ($kwargs as $k => $arg) {
|
8071 |
+
if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
|
8072 |
return null;
|
8073 |
}
|
8074 |
}
|
8075 |
|
8076 |
foreach ($args_to_check as $k => $arg) {
|
8077 |
+
if (in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) && in_array($arg[1], ['min', 'max'])) {
|
8078 |
if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
|
8079 |
return null;
|
8080 |
}
|
8081 |
|
8082 |
$args[$k] = $this->stringifyFncallArgs($arg);
|
|
|
8083 |
}
|
8084 |
|
8085 |
if (
|
8091 |
}
|
8092 |
}
|
8093 |
|
8094 |
+
$hue = $this->reduce($args[0]);
|
8095 |
+
$saturation = $this->reduce($args[1]);
|
8096 |
+
$lightness = $this->reduce($args[2]);
|
8097 |
$alpha = null;
|
8098 |
|
8099 |
if (\count($args) === 4) {
|
8100 |
$alpha = $this->compileColorPartValue($args[3], 0, 100, false);
|
8101 |
|
8102 |
+
if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number || ! is_numeric($alpha)) {
|
8103 |
return [Type::T_STRING, '',
|
8104 |
[$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ', ', $args[3], ')']];
|
8105 |
}
|
8106 |
} else {
|
8107 |
+
if (!$hue instanceof Number || !$saturation instanceof Number || ! $lightness instanceof Number) {
|
8108 |
return [Type::T_STRING, '', [$funcName . '(', $args[0], ', ', $args[1], ', ', $args[2], ')']];
|
8109 |
}
|
8110 |
}
|
8111 |
|
8112 |
+
$hueValue = $hue->getDimension() % 360;
|
8113 |
+
|
8114 |
+
while ($hueValue < 0) {
|
8115 |
+
$hueValue += 360;
|
8116 |
+
}
|
8117 |
+
|
8118 |
+
$color = $this->toRGB($hueValue, max(0, min($saturation->getDimension(), 100)), max(0, min($lightness->getDimension(), 100)));
|
8119 |
|
8120 |
if (! \is_null($alpha)) {
|
8121 |
$color[4] = $alpha;
|
8126 |
|
8127 |
protected static $libHsla = [
|
8128 |
['channels'],
|
8129 |
+
['hue', 'saturation'],
|
8130 |
['hue', 'saturation', 'lightness'],
|
8131 |
['hue', 'saturation', 'lightness', 'alpha']];
|
8132 |
protected function libHsla($args, $kwargs)
|
8137 |
protected static $libHue = ['color'];
|
8138 |
protected function libHue($args)
|
8139 |
{
|
8140 |
+
$color = $this->assertColor($args[0], 'color');
|
8141 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
8142 |
|
8143 |
+
return new Number($hsl[1], 'deg');
|
8144 |
}
|
8145 |
|
8146 |
protected static $libSaturation = ['color'];
|
8147 |
protected function libSaturation($args)
|
8148 |
{
|
8149 |
+
$color = $this->assertColor($args[0], 'color');
|
8150 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
8151 |
|
8152 |
+
return new Number($hsl[2], '%');
|
8153 |
}
|
8154 |
|
8155 |
protected static $libLightness = ['color'];
|
8156 |
protected function libLightness($args)
|
8157 |
{
|
8158 |
+
$color = $this->assertColor($args[0], 'color');
|
8159 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
8160 |
|
8161 |
+
return new Number($hsl[3], '%');
|
8162 |
+
}
|
8163 |
+
|
8164 |
+
/*
|
8165 |
+
* Todo : a integrer dans le futur module color
|
8166 |
+
protected static $libHwb = [
|
8167 |
+
['channels'],
|
8168 |
+
['hue', 'whiteness', 'blackness'],
|
8169 |
+
['hue', 'whiteness', 'blackness', 'alpha'] ];
|
8170 |
+
protected function libHwb($args, $kwargs, $funcName = 'hwb')
|
8171 |
+
{
|
8172 |
+
$args_to_check = $args;
|
8173 |
+
|
8174 |
+
if (\count($args) == 1) {
|
8175 |
+
if ($args[0][0] !== Type::T_LIST) {
|
8176 |
+
throw $this->error("Missing elements \$whiteness and \$blackness");
|
8177 |
+
}
|
8178 |
+
|
8179 |
+
if (\trim($args[0][1])) {
|
8180 |
+
throw $this->error("\$channels must be a space-separated list.");
|
8181 |
+
}
|
8182 |
+
|
8183 |
+
if (! empty($args[0]['enclosing'])) {
|
8184 |
+
throw $this->error("\$channels must be an unbracketed list.");
|
8185 |
+
}
|
8186 |
+
|
8187 |
+
$args = $args[0][2];
|
8188 |
+
if (\count($args) > 3) {
|
8189 |
+
throw $this->error("hwb() : Only 3 elements are allowed but ". \count($args) . "were passed");
|
8190 |
+
}
|
8191 |
+
|
8192 |
+
$args_to_check = $this->extractSlashAlphaInColorFunction($kwargs['channels'][2]);
|
8193 |
+
if (\count($args_to_check) !== \count($kwargs['channels'][2])) {
|
8194 |
+
$args = $args_to_check;
|
8195 |
+
}
|
8196 |
+
}
|
8197 |
+
|
8198 |
+
if (\count($args_to_check) < 2) {
|
8199 |
+
throw $this->error("Missing elements \$whiteness and \$blackness");
|
8200 |
+
}
|
8201 |
+
if (\count($args_to_check) < 3) {
|
8202 |
+
throw $this->error("Missing element \$blackness");
|
8203 |
+
}
|
8204 |
+
if (\count($args_to_check) > 4) {
|
8205 |
+
throw $this->error("hwb() : Only 4 elements are allowed but ". \count($args) . "were passed");
|
8206 |
+
}
|
8207 |
+
|
8208 |
+
foreach ($kwargs as $k => $arg) {
|
8209 |
+
if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
|
8210 |
+
return null;
|
8211 |
+
}
|
8212 |
+
}
|
8213 |
+
|
8214 |
+
foreach ($args_to_check as $k => $arg) {
|
8215 |
+
if (in_array($arg[0], [Type::T_FUNCTION_CALL]) && in_array($arg[1], ['min', 'max'])) {
|
8216 |
+
if (count($kwargs) > 1 || ($k >= 2 && count($args) === 4)) {
|
8217 |
+
return null;
|
8218 |
+
}
|
8219 |
+
|
8220 |
+
$args[$k] = $this->stringifyFncallArgs($arg);
|
8221 |
+
}
|
8222 |
+
|
8223 |
+
if (
|
8224 |
+
$k >= 2 && count($args) === 4 &&
|
8225 |
+
in_array($arg[0], [Type::T_FUNCTION_CALL, Type::T_FUNCTION]) &&
|
8226 |
+
in_array($arg[1], ['calc','env'])
|
8227 |
+
) {
|
8228 |
+
return null;
|
8229 |
+
}
|
8230 |
+
}
|
8231 |
+
|
8232 |
+
$hue = $this->reduce($args[0]);
|
8233 |
+
$whiteness = $this->reduce($args[1]);
|
8234 |
+
$blackness = $this->reduce($args[2]);
|
8235 |
+
$alpha = null;
|
8236 |
+
|
8237 |
+
if (\count($args) === 4) {
|
8238 |
+
$alpha = $this->compileColorPartValue($args[3], 0, 1, false);
|
8239 |
+
|
8240 |
+
if (! \is_numeric($alpha)) {
|
8241 |
+
$val = $this->compileValue($args[3]);
|
8242 |
+
throw $this->error("\$alpha: $val is not a number");
|
8243 |
+
}
|
8244 |
+
}
|
8245 |
+
|
8246 |
+
$this->assertNumber($hue, 'hue');
|
8247 |
+
$this->assertUnit($whiteness, ['%'], 'whiteness');
|
8248 |
+
$this->assertUnit($blackness, ['%'], 'blackness');
|
8249 |
+
|
8250 |
+
$this->assertRange($whiteness, 0, 100, "0% and 100%", "whiteness");
|
8251 |
+
$this->assertRange($blackness, 0, 100, "0% and 100%", "blackness");
|
8252 |
+
|
8253 |
+
$w = $whiteness->getDimension();
|
8254 |
+
$b = $blackness->getDimension();
|
8255 |
+
|
8256 |
+
$hueValue = $hue->getDimension() % 360;
|
8257 |
+
|
8258 |
+
while ($hueValue < 0) {
|
8259 |
+
$hueValue += 360;
|
8260 |
+
}
|
8261 |
+
|
8262 |
+
$color = $this->HWBtoRGB($hueValue, $w, $b);
|
8263 |
+
|
8264 |
+
if (! \is_null($alpha)) {
|
8265 |
+
$color[4] = $alpha;
|
8266 |
+
}
|
8267 |
+
|
8268 |
+
return $color;
|
8269 |
+
}
|
8270 |
+
|
8271 |
+
protected static $libWhiteness = ['color'];
|
8272 |
+
protected function libWhiteness($args, $kwargs, $funcName = 'whiteness') {
|
8273 |
+
|
8274 |
+
$color = $this->assertColor($args[0]);
|
8275 |
+
$hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
|
8276 |
+
|
8277 |
+
return new Number($hwb[2], '%');
|
8278 |
}
|
8279 |
|
8280 |
+
protected static $libBlackness = ['color'];
|
8281 |
+
protected function libBlackness($args, $kwargs, $funcName = 'blackness') {
|
8282 |
+
|
8283 |
+
$color = $this->assertColor($args[0]);
|
8284 |
+
$hwb = $this->RGBtoHWB($color[1], $color[2], $color[3]);
|
8285 |
+
|
8286 |
+
return new Number($hwb[3], '%');
|
8287 |
+
}
|
8288 |
+
*/
|
8289 |
+
|
8290 |
protected function adjustHsl($color, $idx, $amount)
|
8291 |
{
|
8292 |
$hsl = $this->toHSL($color[1], $color[2], $color[3]);
|
8303 |
protected static $libAdjustHue = ['color', 'degrees'];
|
8304 |
protected function libAdjustHue($args)
|
8305 |
{
|
8306 |
+
$color = $this->assertColor($args[0], 'color');
|
8307 |
+
$degrees = $this->assertNumber($args[1], 'degrees')->getDimension();
|
8308 |
|
8309 |
return $this->adjustHsl($color, 1, $degrees);
|
8310 |
}
|
8312 |
protected static $libLighten = ['color', 'amount'];
|
8313 |
protected function libLighten($args)
|
8314 |
{
|
8315 |
+
$color = $this->assertColor($args[0], 'color');
|
8316 |
$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
|
8317 |
|
8318 |
return $this->adjustHsl($color, 3, $amount);
|
8321 |
protected static $libDarken = ['color', 'amount'];
|
8322 |
protected function libDarken($args)
|
8323 |
{
|
8324 |
+
$color = $this->assertColor($args[0], 'color');
|
8325 |
$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
|
8326 |
|
8327 |
return $this->adjustHsl($color, 3, -$amount);
|
8332 |
{
|
8333 |
$value = $args[0];
|
8334 |
|
|
|
|
|
|
|
|
|
8335 |
if (count($args) === 1) {
|
8336 |
+
$this->assertNumber($args[0], 'amount');
|
8337 |
+
|
8338 |
+
return null;
|
8339 |
}
|
8340 |
|
8341 |
+
$color = $this->assertColor($value, 'color');
|
8342 |
+
$amount = 100 * $this->coercePercent($this->assertNumber($args[1], 'amount'));
|
8343 |
|
8344 |
return $this->adjustHsl($color, 2, $amount);
|
8345 |
}
|
8347 |
protected static $libDesaturate = ['color', 'amount'];
|
8348 |
protected function libDesaturate($args)
|
8349 |
{
|
8350 |
+
$color = $this->assertColor($args[0], 'color');
|
8351 |
+
$amount = 100 * $this->coercePercent($this->assertNumber($args[1], 'amount'));
|
8352 |
|
8353 |
return $this->adjustHsl($color, 2, -$amount);
|
8354 |
}
|
8358 |
{
|
8359 |
$value = $args[0];
|
8360 |
|
8361 |
+
if ($value instanceof Number) {
|
8362 |
return null;
|
8363 |
}
|
8364 |
|
8365 |
+
return $this->adjustHsl($this->assertColor($value, 'color'), 2, -100);
|
8366 |
}
|
8367 |
|
8368 |
protected static $libComplement = ['color'];
|
8369 |
protected function libComplement($args)
|
8370 |
{
|
8371 |
+
return $this->adjustHsl($this->assertColor($args[0], 'color'), 1, 180);
|
8372 |
}
|
8373 |
|
8374 |
protected static $libInvert = ['color', 'weight:1'];
|
8375 |
protected function libInvert($args)
|
8376 |
{
|
8377 |
+
$value = $args[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
8378 |
|
8379 |
+
if ($value instanceof Number) {
|
8380 |
return null;
|
8381 |
}
|
8382 |
|
8383 |
+
$weight = $this->coercePercent($this->assertNumber($args[1], 'weight'));
|
8384 |
+
|
8385 |
+
$color = $this->assertColor($value, 'color');
|
8386 |
$inverted = $color;
|
8387 |
$inverted[1] = 255 - $inverted[1];
|
8388 |
$inverted[2] = 255 - $inverted[2];
|
8389 |
$inverted[3] = 255 - $inverted[3];
|
8390 |
|
8391 |
if ($weight < 1) {
|
8392 |
+
return $this->libMix([$inverted, $color, new Number($weight, '')]);
|
8393 |
}
|
8394 |
|
8395 |
return $inverted;
|
8399 |
protected static $libOpacify = ['color', 'amount'];
|
8400 |
protected function libOpacify($args)
|
8401 |
{
|
8402 |
+
$color = $this->assertColor($args[0], 'color');
|
8403 |
+
$amount = $this->coercePercent($this->assertNumber($args[1], 'amount'));
|
8404 |
|
8405 |
$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
|
8406 |
$color[4] = min(1, max(0, $color[4]));
|
8418 |
protected static $libTransparentize = ['color', 'amount'];
|
8419 |
protected function libTransparentize($args)
|
8420 |
{
|
8421 |
+
$color = $this->assertColor($args[0], 'color');
|
8422 |
+
$amount = $this->coercePercent($this->assertNumber($args[1], 'amount'));
|
8423 |
|
8424 |
$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
|
8425 |
$color[4] = min(1, max(0, $color[4]));
|
8436 |
protected static $libUnquote = ['string'];
|
8437 |
protected function libUnquote($args)
|
8438 |
{
|
8439 |
+
try {
|
8440 |
+
$str = $this->assertString($args[0], 'string');
|
8441 |
+
} catch (SassScriptException $e) {
|
8442 |
+
$value = $this->compileValue($args[0]);
|
8443 |
+
$fname = $this->getPrettyPath($this->sourceNames[$this->sourceIndex]);
|
8444 |
+
$line = $this->sourceLine;
|
8445 |
+
|
8446 |
+
$message = "Passing $value, a non-string value, to unquote()
|
8447 |
+
will be an error in future versions of Sass.\n on line $line of $fname";
|
8448 |
|
8449 |
+
$this->logger->warn($message, true);
|
8450 |
+
|
8451 |
+
return $args[0];
|
8452 |
}
|
8453 |
|
8454 |
+
$str[1] = '';
|
8455 |
+
|
8456 |
return $str;
|
8457 |
}
|
8458 |
|
8459 |
protected static $libQuote = ['string'];
|
8460 |
protected function libQuote($args)
|
8461 |
{
|
8462 |
+
$value = $this->assertString($args[0], 'string');
|
8463 |
|
8464 |
+
$value[1] = '"';
|
|
|
|
|
8465 |
|
8466 |
+
return $value;
|
8467 |
}
|
8468 |
|
8469 |
protected static $libPercentage = ['number'];
|
8470 |
protected function libPercentage($args)
|
8471 |
{
|
8472 |
+
$num = $this->assertNumber($args[0], 'number');
|
8473 |
+
$num->assertNoUnits('number');
|
8474 |
+
|
8475 |
+
return new Number($num->getDimension() * 100, '%');
|
8476 |
}
|
8477 |
|
8478 |
protected static $libRound = ['number'];
|
8479 |
protected function libRound($args)
|
8480 |
{
|
8481 |
+
$num = $this->assertNumber($args[0], 'number');
|
8482 |
|
8483 |
+
return new Number(round($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
|
8484 |
}
|
8485 |
|
8486 |
protected static $libFloor = ['number'];
|
8487 |
protected function libFloor($args)
|
8488 |
{
|
8489 |
+
$num = $this->assertNumber($args[0], 'number');
|
8490 |
|
8491 |
+
return new Number(floor($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
|
8492 |
}
|
8493 |
|
8494 |
protected static $libCeil = ['number'];
|
8495 |
protected function libCeil($args)
|
8496 |
{
|
8497 |
+
$num = $this->assertNumber($args[0], 'number');
|
8498 |
|
8499 |
+
return new Number(ceil($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
|
8500 |
}
|
8501 |
|
8502 |
protected static $libAbs = ['number'];
|
8503 |
protected function libAbs($args)
|
8504 |
{
|
8505 |
+
$num = $this->assertNumber($args[0], 'number');
|
8506 |
|
8507 |
+
return new Number(abs($num->getDimension()), $num->getNumeratorUnits(), $num->getDenominatorUnits());
|
8508 |
}
|
8509 |
|
8510 |
+
protected static $libMin = ['numbers...'];
|
8511 |
protected function libMin($args)
|
8512 |
{
|
8513 |
+
/**
|
8514 |
+
* @var Number|null
|
8515 |
+
*/
|
8516 |
+
$min = null;
|
8517 |
|
8518 |
+
foreach ($args[0][2] as $arg) {
|
8519 |
+
$number = $this->assertNumber($arg);
|
8520 |
|
8521 |
+
if (\is_null($min) || $min->greaterThan($number)) {
|
8522 |
+
$min = $number;
|
|
|
|
|
|
|
|
|
|
|
|
|
8523 |
}
|
8524 |
}
|
8525 |
|
8526 |
+
if (!\is_null($min)) {
|
8527 |
+
return $min;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8528 |
}
|
8529 |
|
8530 |
+
throw $this->error('At least one argument must be passed.');
|
8531 |
}
|
8532 |
|
8533 |
+
protected static $libMax = ['numbers...'];
|
8534 |
+
protected function libMax($args)
|
|
|
|
|
|
|
|
|
|
|
|
|
8535 |
{
|
8536 |
+
/**
|
8537 |
+
* @var Number|null
|
8538 |
+
*/
|
8539 |
+
$max = null;
|
|
|
|
|
8540 |
|
8541 |
+
foreach ($args[0][2] as $arg) {
|
8542 |
+
$number = $this->assertNumber($arg);
|
8543 |
|
8544 |
+
if (\is_null($max) || $max->lessThan($number)) {
|
8545 |
+
$max = $number;
|
|
|
|
|
|
|
8546 |
}
|
8547 |
+
}
|
8548 |
|
8549 |
+
if (!\is_null($max)) {
|
8550 |
+
return $max;
|
8551 |
}
|
8552 |
|
8553 |
+
throw $this->error('At least one argument must be passed.');
|
8554 |
}
|
8555 |
|
8556 |
protected static $libLength = ['list'];
|
8558 |
{
|
8559 |
$list = $this->coerceList($args[0], ',', true);
|
8560 |
|
8561 |
+
return new Number(\count($list[2]), '');
|
8562 |
}
|
8563 |
|
8564 |
+
protected static $libListSeparator = ['list'];
|
8565 |
protected function libListSeparator($args)
|
8566 |
{
|
|
|
|
|
|
|
|
|
8567 |
if (! \in_array($args[0][0], [Type::T_LIST, Type::T_MAP])) {
|
8568 |
+
return [Type::T_KEYWORD, 'space'];
|
8569 |
}
|
8570 |
|
8571 |
$list = $this->coerceList($args[0]);
|
8572 |
|
8573 |
if (\count($list[2]) <= 1 && empty($list['enclosing'])) {
|
8574 |
+
return [Type::T_KEYWORD, 'space'];
|
8575 |
}
|
8576 |
|
8577 |
if ($list[1] === ',') {
|
8578 |
+
return [Type::T_KEYWORD, 'comma'];
|
8579 |
}
|
8580 |
|
8581 |
+
return [Type::T_KEYWORD, 'space'];
|
8582 |
}
|
8583 |
|
8584 |
protected static $libNth = ['list', 'n'];
|
8585 |
protected function libNth($args)
|
8586 |
{
|
8587 |
$list = $this->coerceList($args[0], ',', false);
|
8588 |
+
$n = $this->assertNumber($args[1])->getDimension();
|
8589 |
|
8590 |
if ($n > 0) {
|
8591 |
$n--;
|
8600 |
protected function libSetNth($args)
|
8601 |
{
|
8602 |
$list = $this->coerceList($args[0]);
|
8603 |
+
$n = $this->assertNumber($args[1])->getDimension();
|
8604 |
|
8605 |
if ($n > 0) {
|
8606 |
$n--;
|
8620 |
protected static $libMapGet = ['map', 'key'];
|
8621 |
protected function libMapGet($args)
|
8622 |
{
|
8623 |
+
$map = $this->assertMap($args[0], 'map');
|
8624 |
$key = $args[1];
|
8625 |
|
8626 |
if (! \is_null($key)) {
|
8639 |
protected static $libMapKeys = ['map'];
|
8640 |
protected function libMapKeys($args)
|
8641 |
{
|
8642 |
+
$map = $this->assertMap($args[0], 'map');
|
8643 |
$keys = $map[1];
|
8644 |
|
8645 |
return [Type::T_LIST, ',', $keys];
|
8648 |
protected static $libMapValues = ['map'];
|
8649 |
protected function libMapValues($args)
|
8650 |
{
|
8651 |
+
$map = $this->assertMap($args[0], 'map');
|
8652 |
$values = $map[2];
|
8653 |
|
8654 |
return [Type::T_LIST, ',', $values];
|
8655 |
}
|
8656 |
|
8657 |
+
protected static $libMapRemove = [
|
8658 |
+
['map'],
|
8659 |
+
['map', 'key', 'keys...'],
|
8660 |
+
];
|
8661 |
protected function libMapRemove($args)
|
8662 |
{
|
8663 |
+
$map = $this->assertMap($args[0], 'map');
|
8664 |
+
|
8665 |
+
if (\count($args) === 1) {
|
8666 |
+
return $map;
|
8667 |
+
}
|
8668 |
|
8669 |
$keys = [];
|
8670 |
+
$keys[] = $this->compileStringContent($this->coerceString($args[1]));
|
8671 |
|
8672 |
+
foreach ($args[2][2] as $key) {
|
8673 |
$keys[] = $this->compileStringContent($this->coerceString($key));
|
8674 |
}
|
8675 |
|
8686 |
protected static $libMapHasKey = ['map', 'key'];
|
8687 |
protected function libMapHasKey($args)
|
8688 |
{
|
8689 |
+
$map = $this->assertMap($args[0], 'map');
|
8690 |
+
|
8691 |
+
return $this->toBool($this->mapHasKey($map, $args[1]));
|
8692 |
+
}
|
8693 |
+
|
8694 |
+
/**
|
8695 |
+
* @param array|Number $keyValue
|
8696 |
+
*
|
8697 |
+
* @return bool
|
8698 |
+
*/
|
8699 |
+
private function mapHasKey(array $map, $keyValue)
|
8700 |
+
{
|
8701 |
+
$key = $this->compileStringContent($this->coerceString($keyValue));
|
8702 |
|
8703 |
for ($i = \count($map[1]) - 1; $i >= 0; $i--) {
|
8704 |
if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
|
8715 |
];
|
8716 |
protected function libMapMerge($args)
|
8717 |
{
|
8718 |
+
$map1 = $this->assertMap($args[0], 'map1');
|
8719 |
+
$map2 = $this->assertMap($args[1], 'map2');
|
8720 |
|
8721 |
foreach ($map2[1] as $i2 => $key2) {
|
8722 |
$key = $this->compileStringContent($this->coerceString($key2));
|
8738 |
protected static $libKeywords = ['args'];
|
8739 |
protected function libKeywords($args)
|
8740 |
{
|
8741 |
+
$value = $args[0];
|
8742 |
+
|
8743 |
+
if ($value[0] !== Type::T_LIST || !isset($value[3]) || !\is_array($value[3])) {
|
8744 |
+
$compiledValue = $this->compileValue($value);
|
8745 |
+
|
8746 |
+
throw SassScriptException::forArgument($compiledValue . ' is not an argument list.', 'args');
|
8747 |
+
}
|
8748 |
|
8749 |
$keys = [];
|
8750 |
$values = [];
|
8751 |
|
8752 |
+
foreach ($this->getArgumentListKeywords($value) as $name => $arg) {
|
8753 |
$keys[] = [Type::T_KEYWORD, $name];
|
8754 |
$values[] = $arg;
|
8755 |
}
|
8764 |
$this->coerceList($list, ' ');
|
8765 |
|
8766 |
if (! empty($list['enclosing']) && $list['enclosing'] === 'bracket') {
|
8767 |
+
return self::$true;
|
8768 |
}
|
8769 |
|
8770 |
+
return self::$false;
|
8771 |
}
|
8772 |
|
8773 |
+
/**
|
8774 |
+
* @param array $list1
|
8775 |
+
* @param array|Number|null $sep
|
8776 |
+
*
|
8777 |
+
* @return string
|
8778 |
+
* @throws CompilerException
|
8779 |
+
*/
|
8780 |
protected function listSeparatorForJoin($list1, $sep)
|
8781 |
{
|
8782 |
if (! isset($sep)) {
|
8858 |
return $res;
|
8859 |
}
|
8860 |
|
8861 |
+
protected static $libZip = ['lists...'];
|
8862 |
protected function libZip($args)
|
8863 |
{
|
8864 |
+
$argLists = [];
|
8865 |
+
foreach ($args[0][2] as $arg) {
|
8866 |
+
$argLists[] = $this->coerceList($arg);
|
8867 |
}
|
8868 |
|
8869 |
$lists = [];
|
8870 |
+
$firstList = array_shift($argLists);
|
8871 |
|
8872 |
$result = [Type::T_LIST, ',', $lists];
|
8873 |
if (! \is_null($firstList)) {
|
8874 |
foreach ($firstList[2] as $key => $item) {
|
8875 |
$list = [Type::T_LIST, '', [$item]];
|
8876 |
|
8877 |
+
foreach ($argLists as $arg) {
|
8878 |
if (isset($arg[2][$key])) {
|
8879 |
$list[2][] = $arg[2][$key];
|
8880 |
} else {
|
8898 |
{
|
8899 |
$value = $args[0];
|
8900 |
|
8901 |
+
return [Type::T_KEYWORD, $this->getTypeOf($value)];
|
8902 |
+
}
|
8903 |
+
|
8904 |
+
/**
|
8905 |
+
* @param array|Number $value
|
8906 |
+
*
|
8907 |
+
* @return string
|
8908 |
+
*/
|
8909 |
+
private function getTypeOf($value)
|
8910 |
+
{
|
8911 |
switch ($value[0]) {
|
8912 |
case Type::T_KEYWORD:
|
8913 |
if ($value === static::$true || $value === static::$false) {
|
8926 |
return 'function';
|
8927 |
|
8928 |
case Type::T_LIST:
|
8929 |
+
if (isset($value[3]) && \is_array($value[3])) {
|
8930 |
return 'arglist';
|
8931 |
}
|
8932 |
|
8939 |
protected static $libUnit = ['number'];
|
8940 |
protected function libUnit($args)
|
8941 |
{
|
8942 |
+
$num = $this->assertNumber($args[0], 'number');
|
|
|
|
|
|
|
|
|
8943 |
|
8944 |
+
return [Type::T_STRING, '"', [$num->unitStr()]];
|
8945 |
}
|
8946 |
|
8947 |
protected static $libUnitless = ['number'];
|
8948 |
protected function libUnitless($args)
|
8949 |
{
|
8950 |
+
$value = $this->assertNumber($args[0], 'number');
|
8951 |
|
8952 |
+
return $this->toBool($value->unitless());
|
8953 |
}
|
8954 |
|
8955 |
protected static $libComparable = [
|
8961 |
list($number1, $number2) = $args;
|
8962 |
|
8963 |
if (
|
8964 |
+
! $number1 instanceof Number ||
|
8965 |
+
! $number2 instanceof Number
|
8966 |
) {
|
8967 |
throw $this->error('Invalid argument(s) for "comparable"');
|
8968 |
}
|
8969 |
|
8970 |
+
return $this->toBool($number1->isComparableTo($number2));
|
|
|
|
|
|
|
8971 |
}
|
8972 |
|
8973 |
protected static $libStrIndex = ['string', 'substring'];
|
8982 |
if (! \strlen($substringContent)) {
|
8983 |
$result = 0;
|
8984 |
} else {
|
8985 |
+
$result = Util::mbStrpos($stringContent, $substringContent);
|
8986 |
}
|
8987 |
|
8988 |
+
return $result === false ? static::$null : new Number($result + 1, '');
|
8989 |
}
|
8990 |
|
8991 |
protected static $libStrInsert = ['string', 'insert', 'index'];
|
9020 |
$string = $this->assertString($args[0], 'string');
|
9021 |
$stringContent = $this->compileStringContent($string);
|
9022 |
|
9023 |
+
return new Number(Util::mbStrlen($stringContent), '');
|
9024 |
}
|
9025 |
|
9026 |
protected static $libStrSlice = ['string', 'start-at', 'end-at:-1'];
|
9027 |
protected function libStrSlice($args)
|
9028 |
{
|
9029 |
+
$string = $this->assertString($args[0], 'string');
|
9030 |
+
$stringContent = $this->compileStringContent($string);
|
9031 |
+
|
9032 |
+
$start = $this->assertNumber($args[1], 'start-at');
|
9033 |
+
$start->assertNoUnits('start-at');
|
9034 |
+
$startInt = $this->assertInteger($start, 'start-at');
|
9035 |
+
$end = $this->assertNumber($args[2], 'end-at');
|
9036 |
+
$end->assertNoUnits('end-at');
|
9037 |
+
$endInt = $this->assertInteger($end, 'end-at');
|
9038 |
+
|
9039 |
+
if ($endInt === 0) {
|
9040 |
+
return [Type::T_STRING, $string[1], []];
|
9041 |
}
|
9042 |
|
9043 |
+
if ($startInt > 0) {
|
9044 |
+
$startInt--;
|
9045 |
+
}
|
9046 |
|
9047 |
+
if ($endInt < 0) {
|
9048 |
+
$endInt = Util::mbStrlen($stringContent) + $endInt;
|
9049 |
+
} else {
|
9050 |
+
$endInt--;
|
9051 |
+
}
|
9052 |
|
9053 |
+
if ($endInt < $startInt) {
|
9054 |
+
return [Type::T_STRING, $string[1], []];
|
9055 |
}
|
9056 |
|
9057 |
+
$length = $endInt - $startInt + 1; // The end of the slice is inclusive
|
|
|
9058 |
|
9059 |
+
$string[2] = [Util::mbSubstr($stringContent, $startInt, $length)];
|
|
|
|
|
9060 |
|
9061 |
return $string;
|
9062 |
}
|
9064 |
protected static $libToLowerCase = ['string'];
|
9065 |
protected function libToLowerCase($args)
|
9066 |
{
|
9067 |
+
$string = $this->assertString($args[0], 'string');
|
9068 |
$stringContent = $this->compileStringContent($string);
|
9069 |
|
9070 |
+
$string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtolower')];
|
9071 |
|
9072 |
return $string;
|
9073 |
}
|
9075 |
protected static $libToUpperCase = ['string'];
|
9076 |
protected function libToUpperCase($args)
|
9077 |
{
|
9078 |
+
$string = $this->assertString($args[0], 'string');
|
9079 |
$stringContent = $this->compileStringContent($string);
|
9080 |
|
9081 |
+
$string[2] = [$this->stringTransformAsciiOnly($stringContent, 'strtoupper')];
|
9082 |
|
9083 |
return $string;
|
9084 |
}
|
9085 |
|
9086 |
+
/**
|
9087 |
+
* Apply a filter on a string content, only on ascii chars
|
9088 |
+
* let extended chars untouched
|
9089 |
+
*
|
9090 |
+
* @param string $stringContent
|
9091 |
+
* @param callable $filter
|
9092 |
+
* @return string
|
9093 |
+
*/
|
9094 |
+
protected function stringTransformAsciiOnly($stringContent, $filter)
|
9095 |
+
{
|
9096 |
+
$mblength = Util::mbStrlen($stringContent);
|
9097 |
+
if ($mblength === strlen($stringContent)) {
|
9098 |
+
return $filter($stringContent);
|
9099 |
+
}
|
9100 |
+
$filteredString = "";
|
9101 |
+
for ($i = 0; $i < $mblength; $i++) {
|
9102 |
+
$char = Util::mbSubstr($stringContent, $i, 1);
|
9103 |
+
if (strlen($char) > 1) {
|
9104 |
+
$filteredString .= $char;
|
9105 |
+
} else {
|
9106 |
+
$filteredString .= $filter($char);
|
9107 |
+
}
|
9108 |
+
}
|
9109 |
+
|
9110 |
+
return $filteredString;
|
9111 |
+
}
|
9112 |
+
|
9113 |
protected static $libFeatureExists = ['feature'];
|
9114 |
protected function libFeatureExists($args)
|
9115 |
{
|
9116 |
+
$string = $this->assertString($args[0], 'feature');
|
9117 |
$name = $this->compileStringContent($string);
|
9118 |
|
9119 |
return $this->toBool(
|
9124 |
protected static $libFunctionExists = ['name'];
|
9125 |
protected function libFunctionExists($args)
|
9126 |
{
|
9127 |
+
$string = $this->assertString($args[0], 'name');
|
9128 |
$name = $this->compileStringContent($string);
|
9129 |
|
9130 |
// user defined functions
|
9131 |
if ($this->has(static::$namespaces['function'] . $name)) {
|
9132 |
+
return self::$true;
|
9133 |
}
|
9134 |
|
9135 |
$name = $this->normalizeName($name);
|
9136 |
|
9137 |
if (isset($this->userFunctions[$name])) {
|
9138 |
+
return self::$true;
|
9139 |
}
|
9140 |
|
9141 |
// built-in functions
|
9147 |
protected static $libGlobalVariableExists = ['name'];
|
9148 |
protected function libGlobalVariableExists($args)
|
9149 |
{
|
9150 |
+
$string = $this->assertString($args[0], 'name');
|
9151 |
$name = $this->compileStringContent($string);
|
9152 |
|
9153 |
+
return $this->toBool($this->has($name, $this->rootEnv));
|
9154 |
}
|
9155 |
|
9156 |
protected static $libMixinExists = ['name'];
|
9157 |
protected function libMixinExists($args)
|
9158 |
{
|
9159 |
+
$string = $this->assertString($args[0], 'name');
|
9160 |
$name = $this->compileStringContent($string);
|
9161 |
|
9162 |
+
return $this->toBool($this->has(static::$namespaces['mixin'] . $name));
|
9163 |
}
|
9164 |
|
9165 |
protected static $libVariableExists = ['name'];
|
9166 |
protected function libVariableExists($args)
|
9167 |
{
|
9168 |
+
$string = $this->assertString($args[0], 'name');
|
9169 |
$name = $this->compileStringContent($string);
|
9170 |
|
9171 |
+
return $this->toBool($this->has($name));
|
9172 |
}
|
9173 |
|
9174 |
+
protected static $libCounter = ['args...'];
|
9175 |
/**
|
9176 |
* Workaround IE7's content counter bug.
|
9177 |
*
|
9181 |
*/
|
9182 |
protected function libCounter($args)
|
9183 |
{
|
9184 |
+
$list = array_map([$this, 'compileValue'], $args[0][2]);
|
9185 |
|
9186 |
return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
|
9187 |
}
|
9189 |
protected static $libRandom = ['limit:null'];
|
9190 |
protected function libRandom($args)
|
9191 |
{
|
9192 |
+
if (isset($args[0]) && $args[0] !== static::$null) {
|
9193 |
+
$n = $this->assertInteger($args[0], 'limit');
|
9194 |
|
9195 |
if ($n < 1) {
|
9196 |
+
throw new SassScriptException("\$limit: Must be greater than 0, was $n.");
|
|
|
|
|
|
|
|
|
9197 |
}
|
9198 |
|
9199 |
+
return new Number(mt_rand(1, $n), '');
|
9200 |
}
|
9201 |
|
9202 |
$max = mt_getrandmax();
|
9203 |
+
return new Number(mt_rand(0, $max - 1) / $max, '');
|
9204 |
}
|
9205 |
|
9206 |
+
protected static $libUniqueId = [];
|
9207 |
protected function libUniqueId()
|
9208 |
{
|
9209 |
static $id;
|
9219 |
return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
|
9220 |
}
|
9221 |
|
9222 |
+
/**
|
9223 |
+
* @param array|Number $value
|
9224 |
+
* @param bool $force_enclosing_display
|
9225 |
+
*
|
9226 |
+
* @return array
|
9227 |
+
*/
|
9228 |
protected function inspectFormatValue($value, $force_enclosing_display = false)
|
9229 |
{
|
9230 |
if ($value === static::$null) {
|
9233 |
|
9234 |
$stringValue = [$value];
|
9235 |
|
9236 |
+
if ($value instanceof Number) {
|
9237 |
+
return [Type::T_STRING, '', $stringValue];
|
9238 |
+
}
|
9239 |
+
|
9240 |
if ($value[0] === Type::T_LIST) {
|
9241 |
if (end($value[2]) === static::$null) {
|
9242 |
array_pop($value[2]);
|
9275 |
/**
|
9276 |
* Preprocess selector args
|
9277 |
*
|
9278 |
+
* @param array $arg
|
9279 |
+
* @param string|null $varname
|
9280 |
+
* @param bool $allowParent
|
9281 |
*
|
9282 |
+
* @return array
|
9283 |
*/
|
9284 |
protected function getSelectorArg($arg, $varname = null, $allowParent = false)
|
9285 |
{
|
9290 |
}
|
9291 |
|
9292 |
if (! $this->checkSelectorArgType($arg)) {
|
|
|
9293 |
$var_value = $this->compileValue($arg);
|
9294 |
+
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);
|
|
|
9295 |
}
|
9296 |
|
9297 |
+
|
9298 |
+
if ($arg[0] === Type::T_STRING) {
|
9299 |
+
$arg[1] = '';
|
9300 |
+
}
|
9301 |
$arg = $this->compileValue($arg);
|
9302 |
|
9303 |
$parsedSelector = [];
|
9304 |
|
9305 |
+
if ($parser->parseSelector($arg, $parsedSelector, true)) {
|
9306 |
$selector = $this->evalSelectors($parsedSelector);
|
9307 |
$gluedSelector = $this->glueFunctionSelectors($selector);
|
9308 |
|
9310 |
foreach ($gluedSelector as $selector) {
|
9311 |
foreach ($selector as $s) {
|
9312 |
if (in_array(static::$selfSelector, $s)) {
|
9313 |
+
throw SassScriptException::forArgument("Parent selectors aren't allowed here.", $varname);
|
|
|
9314 |
}
|
9315 |
}
|
9316 |
}
|
9319 |
return $gluedSelector;
|
9320 |
}
|
9321 |
|
9322 |
+
throw SassScriptException::forArgument("expected more input, invalid selector.", $varname);
|
|
|
9323 |
}
|
9324 |
|
9325 |
/**
|
9349 |
*
|
9350 |
* @param array $selectors
|
9351 |
*
|
9352 |
+
* @return array
|
9353 |
*/
|
9354 |
protected function formatOutputSelector($selectors)
|
9355 |
{
|
9356 |
+
$selectors = $this->collapseSelectorsAsList($selectors);
|
9357 |
|
9358 |
return $selectors;
|
9359 |
}
|
9366 |
$super = $this->getSelectorArg($super, 'super');
|
9367 |
$sub = $this->getSelectorArg($sub, 'sub');
|
9368 |
|
9369 |
+
return $this->toBool($this->isSuperSelector($super, $sub));
|
9370 |
}
|
9371 |
|
9372 |
/**
|
9609 |
$this->extendsMap = [];
|
9610 |
|
9611 |
foreach ($extendee as $es) {
|
9612 |
+
if (\count($es) !== 1) {
|
9613 |
+
throw $this->error('Can\'t extend complex selector.');
|
9614 |
+
}
|
9615 |
+
|
9616 |
// only use the first one
|
9617 |
$this->pushExtends(reset($es), $extender, null);
|
9618 |
}
|
9713 |
* @param array $compound1
|
9714 |
* @param array $compound2
|
9715 |
*
|
9716 |
+
* @return array
|
9717 |
*/
|
9718 |
protected function unifyCompoundSelectors($compound1, $compound2)
|
9719 |
{
|
9829 |
* @param array $part
|
9830 |
* @param array $compound
|
9831 |
*
|
9832 |
+
* @return array|false
|
9833 |
*/
|
9834 |
protected function matchPartInCompound($part, $compound)
|
9835 |
{
|
9924 |
* @param string $tag1
|
9925 |
* @param string $tag2
|
9926 |
*
|
9927 |
+
* @return array|false
|
9928 |
*/
|
9929 |
protected function checkCompatibleTags($tag1, $tag2)
|
9930 |
{
|
9947 |
/**
|
9948 |
* Find the html tag name in a selector parts list
|
9949 |
*
|
9950 |
+
* @param string[] $parts
|
9951 |
*
|
9952 |
+
* @return string
|
9953 |
*/
|
9954 |
protected function findTagName($parts)
|
9955 |
{
|
9986 |
protected static $libScssphpGlob = ['pattern'];
|
9987 |
protected function libScssphpGlob($args)
|
9988 |
{
|
9989 |
+
@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);
|
9990 |
+
|
9991 |
+
$this->logger->warn('The "scssphp-glob" function is deprecated an will be removed in ScssPhp 2.0.', true);
|
9992 |
+
|
9993 |
+
$string = $this->assertString($args[0], 'pattern');
|
9994 |
$pattern = $this->compileStringContent($string);
|
9995 |
$matches = glob($pattern);
|
9996 |
$listParts = [];
|
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 |
+
}
|
scssphp/src/Compiler/Environment.php
CHANGED
@@ -16,16 +16,18 @@ namespace ScssPhp\ScssPhp\Compiler;
|
|
16 |
* Compiler environment
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class Environment
|
21 |
{
|
22 |
/**
|
23 |
-
* @var \ScssPhp\ScssPhp\Block
|
24 |
*/
|
25 |
public $block;
|
26 |
|
27 |
/**
|
28 |
-
* @var \ScssPhp\ScssPhp\Compiler\Environment
|
29 |
*/
|
30 |
public $parent;
|
31 |
|
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 |
|
scssphp/src/Exception/CompilerException.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp\Exception;
|
|
16 |
* Compiler exception
|
17 |
*
|
18 |
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class CompilerException extends \Exception implements SassException
|
21 |
{
|
16 |
* Compiler exception
|
17 |
*
|
18 |
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
19 |
+
*
|
20 |
+
* @internal
|
21 |
*/
|
22 |
class CompilerException extends \Exception implements SassException
|
23 |
{
|
scssphp/src/Exception/ParserException.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp\Exception;
|
|
16 |
* Parser Exception
|
17 |
*
|
18 |
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class ParserException extends \Exception implements SassException
|
21 |
{
|
16 |
* Parser Exception
|
17 |
*
|
18 |
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
19 |
+
*
|
20 |
+
* @internal
|
21 |
*/
|
22 |
class ParserException extends \Exception implements SassException
|
23 |
{
|
scssphp/src/Exception/RangeException.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp\Exception;
|
|
16 |
* Range exception
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class RangeException extends \Exception implements SassException
|
21 |
{
|
16 |
* Range exception
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
19 |
+
*
|
20 |
+
* @internal
|
21 |
*/
|
22 |
class RangeException extends \Exception implements SassException
|
23 |
{
|
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 |
+
}
|
scssphp/src/Exception/ServerException.php
CHANGED
@@ -12,10 +12,14 @@
|
|
12 |
|
13 |
namespace ScssPhp\ScssPhp\Exception;
|
14 |
|
|
|
|
|
15 |
/**
|
16 |
* Server Exception
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class ServerException extends \Exception implements SassException
|
21 |
{
|
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 |
{
|
scssphp/src/Formatter.php
CHANGED
@@ -19,6 +19,8 @@ use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
|
|
19 |
* Base formatter
|
20 |
*
|
21 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
22 |
*/
|
23 |
abstract class Formatter
|
24 |
{
|
@@ -78,7 +80,7 @@ abstract class Formatter
|
|
78 |
protected $currentColumn;
|
79 |
|
80 |
/**
|
81 |
-
* @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator
|
82 |
*/
|
83 |
protected $sourceMapGenerator;
|
84 |
|
@@ -139,6 +141,8 @@ abstract class Formatter
|
|
139 |
* Output lines inside a block
|
140 |
*
|
141 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
|
|
|
|
142 |
*/
|
143 |
protected function blockLines(OutputBlock $block)
|
144 |
{
|
@@ -156,9 +160,13 @@ abstract class Formatter
|
|
156 |
* Output block selectors
|
157 |
*
|
158 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
|
|
|
|
159 |
*/
|
160 |
protected function blockSelectors(OutputBlock $block)
|
161 |
{
|
|
|
|
|
162 |
$inner = $this->indentStr();
|
163 |
|
164 |
$this->write($inner
|
@@ -170,6 +178,8 @@ abstract class Formatter
|
|
170 |
* Output block children
|
171 |
*
|
172 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
|
|
|
|
173 |
*/
|
174 |
protected function blockChildren(OutputBlock $block)
|
175 |
{
|
@@ -182,6 +192,8 @@ abstract class Formatter
|
|
182 |
* Output non-empty block
|
183 |
*
|
184 |
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
|
|
|
|
|
185 |
*/
|
186 |
protected function block(OutputBlock $block)
|
187 |
{
|
@@ -285,6 +297,8 @@ abstract class Formatter
|
|
285 |
* Output content
|
286 |
*
|
287 |
* @param string $str
|
|
|
|
|
288 |
*/
|
289 |
protected function write($str)
|
290 |
{
|
@@ -310,22 +324,39 @@ abstract class Formatter
|
|
310 |
}
|
311 |
|
312 |
if ($this->sourceMapGenerator) {
|
313 |
-
$this->sourceMapGenerator->addMapping(
|
314 |
-
$this->currentLine,
|
315 |
-
$this->currentColumn,
|
316 |
-
$this->currentBlock->sourceLine,
|
317 |
-
//columns from parser are off by one
|
318 |
-
$this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
|
319 |
-
$this->currentBlock->sourceName
|
320 |
-
);
|
321 |
-
|
322 |
$lines = explode("\n", $str);
|
323 |
-
$lineCount = \count($lines);
|
324 |
-
$this->currentLine += $lineCount - 1;
|
325 |
-
|
326 |
$lastLine = array_pop($lines);
|
327 |
|
328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
}
|
330 |
|
331 |
echo $str;
|
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 |
{
|
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;
|
scssphp/src/Formatter/Compact.php
CHANGED
@@ -18,6 +18,10 @@ use ScssPhp\ScssPhp\Formatter;
|
|
18 |
* Compact formatter
|
19 |
*
|
20 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
|
|
|
|
21 |
*/
|
22 |
class Compact extends Formatter
|
23 |
{
|
@@ -26,6 +30,8 @@ class Compact extends Formatter
|
|
26 |
*/
|
27 |
public function __construct()
|
28 |
{
|
|
|
|
|
29 |
$this->indentLevel = 0;
|
30 |
$this->indentChar = '';
|
31 |
$this->break = '';
|
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 = '';
|
scssphp/src/Formatter/Compressed.php
CHANGED
@@ -13,12 +13,13 @@
|
|
13 |
namespace ScssPhp\ScssPhp\Formatter;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Formatter;
|
16 |
-
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
17 |
|
18 |
/**
|
19 |
* Compressed formatter
|
20 |
*
|
21 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
22 |
*/
|
23 |
class Compressed extends Formatter
|
24 |
{
|
@@ -68,6 +69,8 @@ class Compressed extends Formatter
|
|
68 |
*/
|
69 |
protected function blockSelectors(OutputBlock $block)
|
70 |
{
|
|
|
|
|
71 |
$inner = $this->indentStr();
|
72 |
|
73 |
$this->write(
|
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(
|
scssphp/src/Formatter/Crunched.php
CHANGED
@@ -13,12 +13,15 @@
|
|
13 |
namespace ScssPhp\ScssPhp\Formatter;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Formatter;
|
16 |
-
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
17 |
|
18 |
/**
|
19 |
* Crunched formatter
|
20 |
*
|
21 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
|
|
|
|
22 |
*/
|
23 |
class Crunched extends Formatter
|
24 |
{
|
@@ -27,6 +30,8 @@ class Crunched extends Formatter
|
|
27 |
*/
|
28 |
public function __construct()
|
29 |
{
|
|
|
|
|
30 |
$this->indentLevel = 0;
|
31 |
$this->indentChar = ' ';
|
32 |
$this->break = '';
|
@@ -66,6 +71,8 @@ class Crunched extends Formatter
|
|
66 |
*/
|
67 |
protected function blockSelectors(OutputBlock $block)
|
68 |
{
|
|
|
|
|
69 |
$inner = $this->indentStr();
|
70 |
|
71 |
$this->write(
|
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(
|
scssphp/src/Formatter/Debug.php
CHANGED
@@ -13,12 +13,15 @@
|
|
13 |
namespace ScssPhp\ScssPhp\Formatter;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Formatter;
|
16 |
-
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
17 |
|
18 |
/**
|
19 |
* Debug formatter
|
20 |
*
|
21 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
|
|
|
|
22 |
*/
|
23 |
class Debug extends Formatter
|
24 |
{
|
@@ -27,6 +30,8 @@ class Debug extends Formatter
|
|
27 |
*/
|
28 |
public function __construct()
|
29 |
{
|
|
|
|
|
30 |
$this->indentLevel = 0;
|
31 |
$this->indentChar = '';
|
32 |
$this->break = "\n";
|
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";
|
scssphp/src/Formatter/Expanded.php
CHANGED
@@ -13,12 +13,13 @@
|
|
13 |
namespace ScssPhp\ScssPhp\Formatter;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Formatter;
|
16 |
-
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
17 |
|
18 |
/**
|
19 |
* Expanded formatter
|
20 |
*
|
21 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
22 |
*/
|
23 |
class Expanded extends Formatter
|
24 |
{
|
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 |
{
|
scssphp/src/Formatter/Nested.php
CHANGED
@@ -13,13 +13,16 @@
|
|
13 |
namespace ScssPhp\ScssPhp\Formatter;
|
14 |
|
15 |
use ScssPhp\ScssPhp\Formatter;
|
16 |
-
use ScssPhp\ScssPhp\Formatter\OutputBlock;
|
17 |
use ScssPhp\ScssPhp\Type;
|
18 |
|
19 |
/**
|
20 |
* Nested formatter
|
21 |
*
|
22 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
|
|
|
|
23 |
*/
|
24 |
class Nested extends Formatter
|
25 |
{
|
@@ -33,6 +36,8 @@ class Nested extends Formatter
|
|
33 |
*/
|
34 |
public function __construct()
|
35 |
{
|
|
|
|
|
36 |
$this->indentLevel = 0;
|
37 |
$this->indentChar = ' ';
|
38 |
$this->break = "\n";
|
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";
|
scssphp/src/Formatter/OutputBlock.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp\Formatter;
|
|
16 |
* Output block
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class OutputBlock
|
21 |
{
|
@@ -30,37 +32,37 @@ class OutputBlock
|
|
30 |
public $depth;
|
31 |
|
32 |
/**
|
33 |
-
* @var array
|
34 |
*/
|
35 |
public $selectors;
|
36 |
|
37 |
/**
|
38 |
-
* @var
|
39 |
*/
|
40 |
public $lines;
|
41 |
|
42 |
/**
|
43 |
-
* @var
|
44 |
*/
|
45 |
public $children;
|
46 |
|
47 |
/**
|
48 |
-
* @var
|
49 |
*/
|
50 |
public $parent;
|
51 |
|
52 |
/**
|
53 |
-
* @var string
|
54 |
*/
|
55 |
public $sourceName;
|
56 |
|
57 |
/**
|
58 |
-
* @var integer
|
59 |
*/
|
60 |
public $sourceLine;
|
61 |
|
62 |
/**
|
63 |
-
* @var integer
|
64 |
*/
|
65 |
public $sourceColumn;
|
66 |
}
|
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 |
}
|
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 |
+
}
|
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 |
+
}
|
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 |
+
}
|
scssphp/src/Node.php
CHANGED
@@ -16,6 +16,8 @@ namespace ScssPhp\ScssPhp;
|
|
16 |
* Base node
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
abstract class Node
|
21 |
{
|
@@ -30,12 +32,12 @@ abstract class Node
|
|
30 |
public $sourceIndex;
|
31 |
|
32 |
/**
|
33 |
-
* @var
|
34 |
*/
|
35 |
public $sourceLine;
|
36 |
|
37 |
/**
|
38 |
-
* @var
|
39 |
*/
|
40 |
public $sourceColumn;
|
41 |
}
|
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 |
}
|
scssphp/src/Node/Number.php
CHANGED
@@ -12,9 +12,13 @@
|
|
12 |
|
13 |
namespace ScssPhp\ScssPhp\Node;
|
14 |
|
|
|
15 |
use ScssPhp\ScssPhp\Compiler;
|
|
|
|
|
16 |
use ScssPhp\ScssPhp\Node;
|
17 |
use ScssPhp\ScssPhp\Type;
|
|
|
18 |
|
19 |
/**
|
20 |
* Dimension + optional units
|
@@ -26,6 +30,8 @@ use ScssPhp\ScssPhp\Type;
|
|
26 |
* }}
|
27 |
*
|
28 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
29 |
*/
|
30 |
class Number extends Node implements \ArrayAccess
|
31 |
{
|
@@ -41,6 +47,7 @@ class Number extends Node implements \ArrayAccess
|
|
41 |
* @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
|
42 |
*
|
43 |
* @var array
|
|
|
44 |
*/
|
45 |
protected static $unitTable = [
|
46 |
'in' => [
|
@@ -76,74 +83,67 @@ class Number extends Node implements \ArrayAccess
|
|
76 |
/**
|
77 |
* @var integer|float
|
78 |
*/
|
79 |
-
|
80 |
|
81 |
/**
|
82 |
-
* @var
|
|
|
83 |
*/
|
84 |
-
|
85 |
|
86 |
/**
|
87 |
-
*
|
88 |
-
*
|
89 |
-
* @param mixed $dimension
|
90 |
-
* @param mixed $initialUnit
|
91 |
*/
|
92 |
-
|
93 |
-
{
|
94 |
-
$this->type = Type::T_NUMBER;
|
95 |
-
$this->dimension = $dimension;
|
96 |
-
$this->units = \is_array($initialUnit)
|
97 |
-
? $initialUnit
|
98 |
-
: ($initialUnit ? [$initialUnit => 1]
|
99 |
-
: []);
|
100 |
-
}
|
101 |
|
102 |
/**
|
103 |
-
*
|
104 |
*
|
105 |
-
* @param
|
|
|
|
|
106 |
*
|
107 |
-
* @
|
|
|
108 |
*/
|
109 |
-
public function
|
110 |
{
|
111 |
-
if ($
|
112 |
-
|
|
|
|
|
|
|
|
|
113 |
}
|
114 |
|
115 |
-
$dimension = $
|
116 |
-
|
117 |
-
|
118 |
-
$baseUnit = array_keys($units);
|
119 |
-
$baseUnit = reset($baseUnit);
|
120 |
-
$baseUnit = $this->findBaseUnit($baseUnit);
|
121 |
-
if ($baseUnit && isset(static::$unitTable[$baseUnit])) {
|
122 |
-
foreach (static::$unitTable[$baseUnit] as $unit => $conv) {
|
123 |
-
$from = isset($this->units[$unit]) ? $this->units[$unit] : 0;
|
124 |
-
$to = isset($units[$unit]) ? $units[$unit] : 0;
|
125 |
-
$factor = pow($conv, $from - $to);
|
126 |
-
$dimension /= $factor;
|
127 |
-
}
|
128 |
-
}
|
129 |
-
}
|
130 |
-
|
131 |
-
return new Number($dimension, $units);
|
132 |
}
|
133 |
|
134 |
/**
|
135 |
-
*
|
136 |
-
*
|
137 |
-
* @return \ScssPhp\ScssPhp\Node\Number
|
138 |
*/
|
139 |
-
public function
|
140 |
{
|
141 |
-
|
142 |
-
|
143 |
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
145 |
|
146 |
-
|
|
|
|
|
|
|
|
|
|
|
147 |
}
|
148 |
|
149 |
/**
|
@@ -187,13 +187,13 @@ class Number extends Node implements \ArrayAccess
|
|
187 |
return $this->sourceIndex;
|
188 |
|
189 |
case 0:
|
190 |
-
return
|
191 |
|
192 |
case 1:
|
193 |
return $this->dimension;
|
194 |
|
195 |
case 2:
|
196 |
-
return $this->
|
197 |
}
|
198 |
}
|
199 |
|
@@ -202,17 +202,7 @@ class Number extends Node implements \ArrayAccess
|
|
202 |
*/
|
203 |
public function offsetSet($offset, $value)
|
204 |
{
|
205 |
-
|
206 |
-
$this->dimension = $value;
|
207 |
-
} elseif ($offset === 2) {
|
208 |
-
$this->units = $value;
|
209 |
-
} elseif ($offset == -1) {
|
210 |
-
$this->sourceIndex = $value;
|
211 |
-
} elseif ($offset == -2) {
|
212 |
-
$this->sourceLine = $value;
|
213 |
-
} elseif ($offset == -3) {
|
214 |
-
$this->sourceColumn = $value;
|
215 |
-
}
|
216 |
}
|
217 |
|
218 |
/**
|
@@ -220,17 +210,7 @@ class Number extends Node implements \ArrayAccess
|
|
220 |
*/
|
221 |
public function offsetUnset($offset)
|
222 |
{
|
223 |
-
|
224 |
-
$this->dimension = null;
|
225 |
-
} elseif ($offset === 2) {
|
226 |
-
$this->units = null;
|
227 |
-
} elseif ($offset === -1) {
|
228 |
-
$this->sourceIndex = null;
|
229 |
-
} elseif ($offset === -2) {
|
230 |
-
$this->sourceLine = null;
|
231 |
-
} elseif ($offset === -3) {
|
232 |
-
$this->sourceColumn = null;
|
233 |
-
}
|
234 |
}
|
235 |
|
236 |
/**
|
@@ -240,61 +220,311 @@ class Number extends Node implements \ArrayAccess
|
|
240 |
*/
|
241 |
public function unitless()
|
242 |
{
|
243 |
-
return
|
244 |
}
|
245 |
|
246 |
/**
|
247 |
-
*
|
248 |
-
* ie if its units are homogeneous
|
249 |
*
|
250 |
-
* @
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
251 |
*/
|
252 |
-
public function
|
253 |
{
|
254 |
if ($this->unitless()) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
return false;
|
256 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
|
258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
|
260 |
-
|
261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
262 |
|
263 |
-
if (
|
264 |
-
|
265 |
}
|
266 |
|
267 |
-
if (
|
268 |
-
|
269 |
}
|
270 |
-
}
|
271 |
|
272 |
-
|
|
|
273 |
}
|
274 |
|
275 |
/**
|
276 |
-
*
|
277 |
*
|
278 |
-
* @return
|
279 |
*/
|
280 |
-
public function
|
281 |
{
|
282 |
-
$
|
283 |
-
|
284 |
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
}
|
|
|
|
|
|
|
290 |
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
}
|
296 |
|
297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
}
|
299 |
|
300 |
/**
|
@@ -308,35 +538,29 @@ class Number extends Node implements \ArrayAccess
|
|
308 |
{
|
309 |
$dimension = round($this->dimension, self::PRECISION);
|
310 |
|
311 |
-
|
312 |
-
return
|
313 |
-
}
|
314 |
-
|
315 |
-
if (\count($units) > 1 && array_sum($units) === 0) {
|
316 |
-
$dimension = $this->dimension;
|
317 |
-
$units = [];
|
318 |
-
|
319 |
-
$this->normalizeUnits($dimension, $units);
|
320 |
|
321 |
-
|
322 |
-
|
323 |
-
return $unitSize;
|
324 |
-
});
|
325 |
}
|
326 |
|
327 |
-
$
|
|
|
|
|
328 |
|
329 |
-
if ($compiler
|
330 |
-
$this->units = $units;
|
331 |
$unit = $this->unitStr();
|
|
|
|
|
332 |
} else {
|
333 |
-
|
334 |
-
$unit = key($units);
|
335 |
}
|
336 |
|
337 |
$dimension = number_format($dimension, self::PRECISION, '.', '');
|
338 |
|
339 |
-
return
|
340 |
}
|
341 |
|
342 |
/**
|
@@ -348,48 +572,229 @@ class Number extends Node implements \ArrayAccess
|
|
348 |
}
|
349 |
|
350 |
/**
|
351 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
*
|
353 |
-
* @
|
354 |
-
*
|
355 |
-
* @param string
|
|
|
|
|
|
|
356 |
*/
|
357 |
-
private function
|
358 |
{
|
359 |
-
|
360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
361 |
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
}
|
366 |
|
367 |
-
|
368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
|
370 |
-
$
|
371 |
-
|
|
|
|
|
|
|
|
|
|
|
372 |
}
|
373 |
|
374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
375 |
}
|
|
|
|
|
376 |
}
|
377 |
|
378 |
/**
|
379 |
-
*
|
|
|
|
|
|
|
|
|
380 |
*
|
381 |
-
* @
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
*
|
383 |
-
* @
|
|
|
|
|
|
|
384 |
*/
|
385 |
-
private function
|
386 |
{
|
387 |
-
|
388 |
-
|
389 |
-
|
|
|
|
|
|
|
|
|
390 |
}
|
391 |
}
|
392 |
|
393 |
return null;
|
394 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
395 |
}
|
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 |
{
|
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' => [
|
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 |
/**
|
187 |
return $this->sourceIndex;
|
188 |
|
189 |
case 0:
|
190 |
+
return Type::T_NUMBER;
|
191 |
|
192 |
case 1:
|
193 |
return $this->dimension;
|
194 |
|
195 |
case 2:
|
196 |
+
return array('numerator_units' => $this->numeratorUnits, 'denominator_units' => $this->denominatorUnits);
|
197 |
}
|
198 |
}
|
199 |
|
202 |
*/
|
203 |
public function offsetSet($offset, $value)
|
204 |
{
|
205 |
+
throw new \BadMethodCallException('Number is immutable');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
}
|
207 |
|
208 |
/**
|
210 |
*/
|
211 |
public function offsetUnset($offset)
|
212 |
{
|
213 |
+
throw new \BadMethodCallException('Number is immutable');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
}
|
215 |
|
216 |
/**
|
220 |
*/
|
221 |
public function unitless()
|
222 |
{
|
223 |
+
return \count($this->numeratorUnits) === 0 && \count($this->denominatorUnits) === 0;
|
224 |
}
|
225 |
|
226 |
/**
|
227 |
+
* Checks whether the number has exactly this unit
|
|
|
228 |
*
|
229 |
+
* @param string $unit
|
230 |
+
*
|
231 |
+
* @return bool
|
232 |
+
*/
|
233 |
+
public function hasUnit($unit)
|
234 |
+
{
|
235 |
+
return \count($this->numeratorUnits) === 1 && \count($this->denominatorUnits) === 0 && $this->numeratorUnits[0] === $unit;
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Returns unit(s) as the product of numerator units divided by the product of denominator units
|
240 |
+
*
|
241 |
+
* @return string
|
242 |
*/
|
243 |
+
public function unitStr()
|
244 |
{
|
245 |
if ($this->unitless()) {
|
246 |
+
return '';
|
247 |
+
}
|
248 |
+
|
249 |
+
return self::getUnitString($this->numeratorUnits, $this->denominatorUnits);
|
250 |
+
}
|
251 |
+
|
252 |
+
/**
|
253 |
+
* @param float|int $min
|
254 |
+
* @param float|int $max
|
255 |
+
* @param string|null $name
|
256 |
+
*
|
257 |
+
* @return float|int
|
258 |
+
* @throws SassScriptException
|
259 |
+
*/
|
260 |
+
public function valueInRange($min, $max, $name = null)
|
261 |
+
{
|
262 |
+
try {
|
263 |
+
return Util::checkRange('', new Range($min, $max), $this);
|
264 |
+
} catch (RangeException $e) {
|
265 |
+
throw SassScriptException::forArgument(sprintf('Expected %s to be within %s%s and %s%3$s', $this, $min, $this->unitStr(), $max), $name);
|
266 |
+
}
|
267 |
+
}
|
268 |
+
|
269 |
+
/**
|
270 |
+
* @param string|null $varName
|
271 |
+
*
|
272 |
+
* @return void
|
273 |
+
*/
|
274 |
+
public function assertNoUnits($varName = null)
|
275 |
+
{
|
276 |
+
if ($this->unitless()) {
|
277 |
+
return;
|
278 |
+
}
|
279 |
+
|
280 |
+
throw SassScriptException::forArgument(sprintf('Expected %s to have no units.', $this), $varName);
|
281 |
+
}
|
282 |
+
|
283 |
+
/**
|
284 |
+
* @param string $unit
|
285 |
+
* @param string|null $varName
|
286 |
+
*
|
287 |
+
* @return void
|
288 |
+
*/
|
289 |
+
public function assertUnit($unit, $varName = null)
|
290 |
+
{
|
291 |
+
if ($this->hasUnit($unit)) {
|
292 |
+
return;
|
293 |
+
}
|
294 |
+
|
295 |
+
throw SassScriptException::forArgument(sprintf('Expected %s to have unit "%s".', $this, $unit), $varName);
|
296 |
+
}
|
297 |
+
|
298 |
+
/**
|
299 |
+
* @param Number $other
|
300 |
+
*
|
301 |
+
* @return void
|
302 |
+
*/
|
303 |
+
public function assertSameUnitOrUnitless(Number $other)
|
304 |
+
{
|
305 |
+
if ($other->unitless()) {
|
306 |
+
return;
|
307 |
+
}
|
308 |
+
|
309 |
+
if ($this->numeratorUnits === $other->numeratorUnits && $this->denominatorUnits === $other->denominatorUnits) {
|
310 |
+
return;
|
311 |
+
}
|
312 |
+
|
313 |
+
throw new SassScriptException(sprintf(
|
314 |
+
'Incompatible units %s and %s.',
|
315 |
+
self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
|
316 |
+
self::getUnitString($other->numeratorUnits, $other->denominatorUnits)
|
317 |
+
));
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Returns a copy of this number, converted to the units represented by $newNumeratorUnits and $newDenominatorUnits.
|
322 |
+
*
|
323 |
+
* This does not throw an error if this number is unitless and
|
324 |
+
* $newNumeratorUnits/$newDenominatorUnits are not empty, or vice versa. Instead,
|
325 |
+
* it treats all unitless numbers as convertible to and from all units without
|
326 |
+
* changing the value.
|
327 |
+
*
|
328 |
+
* @param string[] $newNumeratorUnits
|
329 |
+
* @param string[] $newDenominatorUnits
|
330 |
+
*
|
331 |
+
* @return Number
|
332 |
+
*
|
333 |
+
* @phpstan-param list<string> $newNumeratorUnits
|
334 |
+
* @phpstan-param list<string> $newDenominatorUnits
|
335 |
+
*
|
336 |
+
* @throws SassScriptException if this number's units are not compatible with $newNumeratorUnits and $newDenominatorUnits
|
337 |
+
*/
|
338 |
+
public function coerce(array $newNumeratorUnits, array $newDenominatorUnits)
|
339 |
+
{
|
340 |
+
return new Number($this->valueInUnits($newNumeratorUnits, $newDenominatorUnits), $newNumeratorUnits, $newDenominatorUnits);
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* @param Number $other
|
345 |
+
*
|
346 |
+
* @return bool
|
347 |
+
*/
|
348 |
+
public function isComparableTo(Number $other)
|
349 |
+
{
|
350 |
+
if ($this->unitless() || $other->unitless()) {
|
351 |
+
return true;
|
352 |
+
}
|
353 |
+
|
354 |
+
try {
|
355 |
+
$this->greaterThan($other);
|
356 |
+
return true;
|
357 |
+
} catch (SassScriptException $e) {
|
358 |
return false;
|
359 |
}
|
360 |
+
}
|
361 |
+
|
362 |
+
/**
|
363 |
+
* @param Number $other
|
364 |
+
*
|
365 |
+
* @return bool
|
366 |
+
*/
|
367 |
+
public function lessThan(Number $other)
|
368 |
+
{
|
369 |
+
return $this->coerceUnits($other, function ($num1, $num2) {
|
370 |
+
return $num1 < $num2;
|
371 |
+
});
|
372 |
+
}
|
373 |
+
|
374 |
+
/**
|
375 |
+
* @param Number $other
|
376 |
+
*
|
377 |
+
* @return bool
|
378 |
+
*/
|
379 |
+
public function lessThanOrEqual(Number $other)
|
380 |
+
{
|
381 |
+
return $this->coerceUnits($other, function ($num1, $num2) {
|
382 |
+
return $num1 <= $num2;
|
383 |
+
});
|
384 |
+
}
|
385 |
+
|
386 |
+
/**
|
387 |
+
* @param Number $other
|
388 |
+
*
|
389 |
+
* @return bool
|
390 |
+
*/
|
391 |
+
public function greaterThan(Number $other)
|
392 |
+
{
|
393 |
+
return $this->coerceUnits($other, function ($num1, $num2) {
|
394 |
+
return $num1 > $num2;
|
395 |
+
});
|
396 |
+
}
|
397 |
+
|
398 |
+
/**
|
399 |
+
* @param Number $other
|
400 |
+
*
|
401 |
+
* @return bool
|
402 |
+
*/
|
403 |
+
public function greaterThanOrEqual(Number $other)
|
404 |
+
{
|
405 |
+
return $this->coerceUnits($other, function ($num1, $num2) {
|
406 |
+
return $num1 >= $num2;
|
407 |
+
});
|
408 |
+
}
|
409 |
+
|
410 |
+
/**
|
411 |
+
* @param Number $other
|
412 |
+
*
|
413 |
+
* @return Number
|
414 |
+
*/
|
415 |
+
public function plus(Number $other)
|
416 |
+
{
|
417 |
+
return $this->coerceNumber($other, function ($num1, $num2) {
|
418 |
+
return $num1 + $num2;
|
419 |
+
});
|
420 |
+
}
|
421 |
+
|
422 |
+
/**
|
423 |
+
* @param Number $other
|
424 |
+
*
|
425 |
+
* @return Number
|
426 |
+
*/
|
427 |
+
public function minus(Number $other)
|
428 |
+
{
|
429 |
+
return $this->coerceNumber($other, function ($num1, $num2) {
|
430 |
+
return $num1 - $num2;
|
431 |
+
});
|
432 |
+
}
|
433 |
|
434 |
+
/**
|
435 |
+
* @return Number
|
436 |
+
*/
|
437 |
+
public function unaryMinus()
|
438 |
+
{
|
439 |
+
return new Number(-$this->dimension, $this->numeratorUnits, $this->denominatorUnits);
|
440 |
+
}
|
441 |
|
442 |
+
/**
|
443 |
+
* @param Number $other
|
444 |
+
*
|
445 |
+
* @return Number
|
446 |
+
*/
|
447 |
+
public function modulo(Number $other)
|
448 |
+
{
|
449 |
+
return $this->coerceNumber($other, function ($num1, $num2) {
|
450 |
+
if ($num2 == 0) {
|
451 |
+
return NAN;
|
452 |
+
}
|
453 |
+
|
454 |
+
$result = fmod($num1, $num2);
|
455 |
|
456 |
+
if ($result == 0) {
|
457 |
+
return 0;
|
458 |
}
|
459 |
|
460 |
+
if ($num2 < 0 xor $num1 < 0) {
|
461 |
+
$result += $num2;
|
462 |
}
|
|
|
463 |
|
464 |
+
return $result;
|
465 |
+
});
|
466 |
}
|
467 |
|
468 |
/**
|
469 |
+
* @param Number $other
|
470 |
*
|
471 |
+
* @return Number
|
472 |
*/
|
473 |
+
public function times(Number $other)
|
474 |
{
|
475 |
+
return $this->multiplyUnits($this->dimension * $other->dimension, $this->numeratorUnits, $this->denominatorUnits, $other->numeratorUnits, $other->denominatorUnits);
|
476 |
+
}
|
477 |
|
478 |
+
/**
|
479 |
+
* @param Number $other
|
480 |
+
*
|
481 |
+
* @return Number
|
482 |
+
*/
|
483 |
+
public function dividedBy(Number $other)
|
484 |
+
{
|
485 |
+
if ($other->dimension == 0) {
|
486 |
+
if ($this->dimension == 0) {
|
487 |
+
$value = NAN;
|
488 |
+
} elseif ($this->dimension > 0) {
|
489 |
+
$value = INF;
|
490 |
+
} else {
|
491 |
+
$value = -INF;
|
492 |
}
|
493 |
+
} else {
|
494 |
+
$value = $this->dimension / $other->dimension;
|
495 |
+
}
|
496 |
|
497 |
+
return $this->multiplyUnits($value, $this->numeratorUnits, $this->denominatorUnits, $other->denominatorUnits, $other->numeratorUnits);
|
498 |
+
}
|
499 |
+
|
500 |
+
/**
|
501 |
+
* @param Number $other
|
502 |
+
*
|
503 |
+
* @return bool
|
504 |
+
*/
|
505 |
+
public function equals(Number $other)
|
506 |
+
{
|
507 |
+
// Unitless numbers are convertable to unit numbers, but not equal, so we special-case unitless here.
|
508 |
+
if ($this->unitless() !== $other->unitless()) {
|
509 |
+
return false;
|
510 |
}
|
511 |
|
512 |
+
// In Sass, neither NaN nor Infinity are equal to themselves, while PHP defines INF==INF
|
513 |
+
if (is_nan($this->dimension) || is_nan($other->dimension) || !is_finite($this->dimension) || !is_finite($other->dimension)) {
|
514 |
+
return false;
|
515 |
+
}
|
516 |
+
|
517 |
+
if ($this->unitless()) {
|
518 |
+
return round($this->dimension, self::PRECISION) == round($other->dimension, self::PRECISION);
|
519 |
+
}
|
520 |
+
|
521 |
+
try {
|
522 |
+
return $this->coerceUnits($other, function ($num1, $num2) {
|
523 |
+
return round($num1,self::PRECISION) == round($num2, self::PRECISION);
|
524 |
+
});
|
525 |
+
} catch (SassScriptException $e) {
|
526 |
+
return false;
|
527 |
+
}
|
528 |
}
|
529 |
|
530 |
/**
|
538 |
{
|
539 |
$dimension = round($this->dimension, self::PRECISION);
|
540 |
|
541 |
+
if (is_nan($dimension)) {
|
542 |
+
return 'NaN';
|
543 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
544 |
|
545 |
+
if ($dimension === INF) {
|
546 |
+
return 'Infinity';
|
|
|
|
|
547 |
}
|
548 |
|
549 |
+
if ($dimension === -INF) {
|
550 |
+
return '-Infinity';
|
551 |
+
}
|
552 |
|
553 |
+
if ($compiler) {
|
|
|
554 |
$unit = $this->unitStr();
|
555 |
+
} elseif (isset($this->numeratorUnits[0])) {
|
556 |
+
$unit = $this->numeratorUnits[0];
|
557 |
} else {
|
558 |
+
$unit = '';
|
|
|
559 |
}
|
560 |
|
561 |
$dimension = number_format($dimension, self::PRECISION, '.', '');
|
562 |
|
563 |
+
return rtrim(rtrim($dimension, '0'), '.') . $unit;
|
564 |
}
|
565 |
|
566 |
/**
|
572 |
}
|
573 |
|
574 |
/**
|
575 |
+
* @param Number $other
|
576 |
+
* @param callable $operation
|
577 |
+
*
|
578 |
+
* @return Number
|
579 |
+
*
|
580 |
+
* @phpstan-param callable(int|float, int|float): (int|float) $operation
|
581 |
+
*/
|
582 |
+
private function coerceNumber(Number $other, $operation)
|
583 |
+
{
|
584 |
+
$result = $this->coerceUnits($other, $operation);
|
585 |
+
|
586 |
+
if (!$this->unitless()) {
|
587 |
+
return new Number($result, $this->numeratorUnits, $this->denominatorUnits);
|
588 |
+
}
|
589 |
+
|
590 |
+
return new Number($result, $other->numeratorUnits, $other->denominatorUnits);
|
591 |
+
}
|
592 |
+
|
593 |
+
/**
|
594 |
+
* @param Number $other
|
595 |
+
* @param callable $operation
|
596 |
+
*
|
597 |
+
* @return mixed
|
598 |
+
*
|
599 |
+
* @phpstan-template T
|
600 |
+
* @phpstan-param callable(int|float, int|float): T $operation
|
601 |
+
* @phpstan-return T
|
602 |
+
*/
|
603 |
+
private function coerceUnits(Number $other, $operation)
|
604 |
+
{
|
605 |
+
if (!$this->unitless()) {
|
606 |
+
$num1 = $this->dimension;
|
607 |
+
$num2 = $other->valueInUnits($this->numeratorUnits, $this->denominatorUnits);
|
608 |
+
} else {
|
609 |
+
$num1 = $this->valueInUnits($other->numeratorUnits, $other->denominatorUnits);
|
610 |
+
$num2 = $other->dimension;
|
611 |
+
}
|
612 |
+
|
613 |
+
return \call_user_func($operation, $num1, $num2);
|
614 |
+
}
|
615 |
+
|
616 |
+
/**
|
617 |
+
* @param string[] $numeratorUnits
|
618 |
+
* @param string[] $denominatorUnits
|
619 |
*
|
620 |
+
* @return int|float
|
621 |
+
*
|
622 |
+
* @phpstan-param list<string> $numeratorUnits
|
623 |
+
* @phpstan-param list<string> $denominatorUnits
|
624 |
+
*
|
625 |
+
* @throws SassScriptException if this number's units are not compatible with $numeratorUnits and $denominatorUnits
|
626 |
*/
|
627 |
+
private function valueInUnits(array $numeratorUnits, array $denominatorUnits)
|
628 |
{
|
629 |
+
if (
|
630 |
+
$this->unitless()
|
631 |
+
|| (\count($numeratorUnits) === 0 && \count($denominatorUnits) === 0)
|
632 |
+
|| ($this->numeratorUnits === $numeratorUnits && $this->denominatorUnits === $denominatorUnits)
|
633 |
+
) {
|
634 |
+
return $this->dimension;
|
635 |
+
}
|
636 |
+
|
637 |
+
$value = $this->dimension;
|
638 |
+
$oldNumerators = $this->numeratorUnits;
|
639 |
+
|
640 |
+
foreach ($numeratorUnits as $newNumerator) {
|
641 |
+
foreach ($oldNumerators as $key => $oldNumerator) {
|
642 |
+
$conversionFactor = self::getConversionFactor($newNumerator, $oldNumerator);
|
643 |
+
|
644 |
+
if (\is_null($conversionFactor)) {
|
645 |
+
continue;
|
646 |
+
}
|
647 |
|
648 |
+
$value *= $conversionFactor;
|
649 |
+
unset($oldNumerators[$key]);
|
650 |
+
continue 2;
|
651 |
}
|
652 |
|
653 |
+
throw new SassScriptException(sprintf(
|
654 |
+
'Incompatible units %s and %s.',
|
655 |
+
self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
|
656 |
+
self::getUnitString($numeratorUnits, $denominatorUnits)
|
657 |
+
));
|
658 |
+
}
|
659 |
+
|
660 |
+
$oldDenominators = $this->denominatorUnits;
|
661 |
+
|
662 |
+
foreach ($denominatorUnits as $newDenominator) {
|
663 |
+
foreach ($oldDenominators as $key => $oldDenominator) {
|
664 |
+
$conversionFactor = self::getConversionFactor($newDenominator, $oldDenominator);
|
665 |
|
666 |
+
if (\is_null($conversionFactor)) {
|
667 |
+
continue;
|
668 |
+
}
|
669 |
+
|
670 |
+
$value /= $conversionFactor;
|
671 |
+
unset($oldDenominators[$key]);
|
672 |
+
continue 2;
|
673 |
}
|
674 |
|
675 |
+
throw new SassScriptException(sprintf(
|
676 |
+
'Incompatible units %s and %s.',
|
677 |
+
self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
|
678 |
+
self::getUnitString($numeratorUnits, $denominatorUnits)
|
679 |
+
));
|
680 |
+
}
|
681 |
+
|
682 |
+
if (\count($oldNumerators) || \count($oldDenominators)) {
|
683 |
+
throw new SassScriptException(sprintf(
|
684 |
+
'Incompatible units %s and %s.',
|
685 |
+
self::getUnitString($this->numeratorUnits, $this->denominatorUnits),
|
686 |
+
self::getUnitString($numeratorUnits, $denominatorUnits)
|
687 |
+
));
|
688 |
}
|
689 |
+
|
690 |
+
return $value;
|
691 |
}
|
692 |
|
693 |
/**
|
694 |
+
* @param int|float $value
|
695 |
+
* @param string[] $numerators1
|
696 |
+
* @param string[] $denominators1
|
697 |
+
* @param string[] $numerators2
|
698 |
+
* @param string[] $denominators2
|
699 |
*
|
700 |
+
* @return Number
|
701 |
+
*
|
702 |
+
* @phpstan-param list<string> $numerators1
|
703 |
+
* @phpstan-param list<string> $denominators1
|
704 |
+
* @phpstan-param list<string> $numerators2
|
705 |
+
* @phpstan-param list<string> $denominators2
|
706 |
+
*/
|
707 |
+
private function multiplyUnits($value, array $numerators1, array $denominators1, array $numerators2, array $denominators2)
|
708 |
+
{
|
709 |
+
$newNumerators = array();
|
710 |
+
|
711 |
+
foreach ($numerators1 as $numerator) {
|
712 |
+
foreach ($denominators2 as $key => $denominator) {
|
713 |
+
$conversionFactor = self::getConversionFactor($numerator, $denominator);
|
714 |
+
|
715 |
+
if (\is_null($conversionFactor)) {
|
716 |
+
continue;
|
717 |
+
}
|
718 |
+
|
719 |
+
$value /= $conversionFactor;
|
720 |
+
unset($denominators2[$key]);
|
721 |
+
continue 2;
|
722 |
+
}
|
723 |
+
|
724 |
+
$newNumerators[] = $numerator;
|
725 |
+
}
|
726 |
+
|
727 |
+
foreach ($numerators2 as $numerator) {
|
728 |
+
foreach ($denominators1 as $key => $denominator) {
|
729 |
+
$conversionFactor = self::getConversionFactor($numerator, $denominator);
|
730 |
+
|
731 |
+
if (\is_null($conversionFactor)) {
|
732 |
+
continue;
|
733 |
+
}
|
734 |
+
|
735 |
+
$value /= $conversionFactor;
|
736 |
+
unset($denominators1[$key]);
|
737 |
+
continue 2;
|
738 |
+
}
|
739 |
+
|
740 |
+
$newNumerators[] = $numerator;
|
741 |
+
}
|
742 |
+
|
743 |
+
$newDenominators = array_values(array_merge($denominators1, $denominators2));
|
744 |
+
|
745 |
+
return new Number($value, $newNumerators, $newDenominators);
|
746 |
+
}
|
747 |
+
|
748 |
+
/**
|
749 |
+
* Returns the number of [unit1]s per [unit2].
|
750 |
+
*
|
751 |
+
* Equivalently, `1unit1 * conversionFactor(unit1, unit2) = 1unit2`.
|
752 |
*
|
753 |
+
* @param string $unit1
|
754 |
+
* @param string $unit2
|
755 |
+
*
|
756 |
+
* @return float|int|null
|
757 |
*/
|
758 |
+
private static function getConversionFactor($unit1, $unit2)
|
759 |
{
|
760 |
+
if ($unit1 === $unit2) {
|
761 |
+
return 1;
|
762 |
+
}
|
763 |
+
|
764 |
+
foreach (static::$unitTable as $unitVariants) {
|
765 |
+
if (isset($unitVariants[$unit1]) && isset($unitVariants[$unit2])) {
|
766 |
+
return $unitVariants[$unit1] / $unitVariants[$unit2];
|
767 |
}
|
768 |
}
|
769 |
|
770 |
return null;
|
771 |
}
|
772 |
+
|
773 |
+
/**
|
774 |
+
* Returns unit(s) as the product of numerator units divided by the product of denominator units
|
775 |
+
*
|
776 |
+
* @param string[] $numerators
|
777 |
+
* @param string[] $denominators
|
778 |
+
*
|
779 |
+
* @phpstan-param list<string> $numerators
|
780 |
+
* @phpstan-param list<string> $denominators
|
781 |
+
*
|
782 |
+
* @return string
|
783 |
+
*/
|
784 |
+
private static function getUnitString(array $numerators, array $denominators)
|
785 |
+
{
|
786 |
+
if (!\count($numerators)) {
|
787 |
+
if (\count($denominators) === 0) {
|
788 |
+
return 'no units';
|
789 |
+
}
|
790 |
+
|
791 |
+
if (\count($denominators) === 1) {
|
792 |
+
return $denominators[0] . '^-1';
|
793 |
+
}
|
794 |
+
|
795 |
+
return '(' . implode('*', $denominators) . ')^-1';
|
796 |
+
}
|
797 |
+
|
798 |
+
return implode('*', $numerators) . (\count($denominators) ? '/' . implode('*', $denominators) : '');
|
799 |
+
}
|
800 |
}
|
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 |
+
}
|
scssphp/src/Parser.php
CHANGED
@@ -12,17 +12,16 @@
|
|
12 |
|
13 |
namespace ScssPhp\ScssPhp;
|
14 |
|
15 |
-
use ScssPhp\ScssPhp\Block;
|
16 |
-
use ScssPhp\ScssPhp\Cache;
|
17 |
-
use ScssPhp\ScssPhp\Compiler;
|
18 |
use ScssPhp\ScssPhp\Exception\ParserException;
|
19 |
-
use ScssPhp\ScssPhp\
|
20 |
-
use ScssPhp\ScssPhp\
|
21 |
|
22 |
/**
|
23 |
* Parser
|
24 |
*
|
25 |
* @author Leaf Corcoran <leafot@gmail.com>
|
|
|
|
|
26 |
*/
|
27 |
class Parser
|
28 |
{
|
@@ -31,7 +30,7 @@ class Parser
|
|
31 |
const SOURCE_COLUMN = -3;
|
32 |
|
33 |
/**
|
34 |
-
* @var array
|
35 |
*/
|
36 |
protected static $precedence = [
|
37 |
'=' => 0,
|
@@ -39,7 +38,6 @@ class Parser
|
|
39 |
'and' => 2,
|
40 |
'==' => 3,
|
41 |
'!=' => 3,
|
42 |
-
'<=>' => 3,
|
43 |
'<=' => 4,
|
44 |
'>=' => 4,
|
45 |
'<' => 4,
|
@@ -51,41 +49,89 @@ class Parser
|
|
51 |
'%' => 6,
|
52 |
];
|
53 |
|
|
|
|
|
|
|
54 |
protected static $commentPattern;
|
|
|
|
|
|
|
55 |
protected static $operatorPattern;
|
|
|
|
|
|
|
56 |
protected static $whitePattern;
|
57 |
|
|
|
|
|
|
|
58 |
protected $cache;
|
59 |
|
60 |
private $sourceName;
|
61 |
private $sourceIndex;
|
|
|
|
|
|
|
62 |
private $sourcePositions;
|
|
|
|
|
|
|
63 |
private $charset;
|
|
|
|
|
|
|
|
|
|
|
64 |
private $count;
|
|
|
|
|
|
|
65 |
private $env;
|
|
|
|
|
|
|
66 |
private $inParens;
|
|
|
|
|
|
|
67 |
private $eatWhiteDefault;
|
|
|
|
|
|
|
68 |
private $discardComments;
|
69 |
private $allowVars;
|
|
|
|
|
|
|
70 |
private $buffer;
|
71 |
private $utf8;
|
|
|
|
|
|
|
72 |
private $encoding;
|
73 |
private $patternModifiers;
|
74 |
private $commentsSeen;
|
75 |
|
76 |
private $cssOnly;
|
77 |
|
|
|
|
|
|
|
|
|
|
|
78 |
/**
|
79 |
* Constructor
|
80 |
*
|
81 |
* @api
|
82 |
*
|
83 |
-
* @param string
|
84 |
-
* @param integer
|
85 |
-
* @param string
|
86 |
-
* @param
|
|
|
|
|
87 |
*/
|
88 |
-
public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $cache = null, $cssOnly = false)
|
89 |
{
|
90 |
$this->sourceName = $sourceName ?: '(stdin)';
|
91 |
$this->sourceIndex = $sourceIndex;
|
@@ -96,9 +142,10 @@ class Parser
|
|
96 |
$this->commentsSeen = [];
|
97 |
$this->allowVars = true;
|
98 |
$this->cssOnly = $cssOnly;
|
|
|
99 |
|
100 |
if (empty(static::$operatorPattern)) {
|
101 |
-
static::$operatorPattern = '([*\/%+-]|[!=]
|
102 |
|
103 |
$commentSingle = '\/\/';
|
104 |
$commentMultiLeft = '\/\*';
|
@@ -110,9 +157,7 @@ class Parser
|
|
110 |
: '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
|
111 |
}
|
112 |
|
113 |
-
|
114 |
-
$this->cache = $cache;
|
115 |
-
}
|
116 |
}
|
117 |
|
118 |
/**
|
@@ -134,9 +179,32 @@ class Parser
|
|
134 |
*
|
135 |
* @param string $msg
|
136 |
*
|
137 |
-
* @
|
|
|
|
|
|
|
|
|
138 |
*/
|
139 |
public function throwParseError($msg = 'parse error')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
{
|
141 |
list($line, $column) = $this->getSourcePosition($this->count);
|
142 |
|
@@ -150,7 +218,7 @@ class Parser
|
|
150 |
$e = new ParserException("$msg: failed at `$m[1]` $loc");
|
151 |
$e->setSourcePosition([$this->sourceName, $line, $column]);
|
152 |
|
153 |
-
|
154 |
}
|
155 |
|
156 |
$this->restoreEncoding();
|
@@ -158,7 +226,7 @@ class Parser
|
|
158 |
$e = new ParserException("$msg: $loc");
|
159 |
$e->setSourcePosition([$this->sourceName, $line, $column]);
|
160 |
|
161 |
-
|
162 |
}
|
163 |
|
164 |
/**
|
@@ -168,7 +236,7 @@ class Parser
|
|
168 |
*
|
169 |
* @param string $buffer
|
170 |
*
|
171 |
-
* @return
|
172 |
*/
|
173 |
public function parse($buffer)
|
174 |
{
|
@@ -209,11 +277,11 @@ class Parser
|
|
209 |
}
|
210 |
|
211 |
if ($this->count !== \strlen($this->buffer)) {
|
212 |
-
$this->
|
213 |
}
|
214 |
|
215 |
if (! empty($this->env->parent)) {
|
216 |
-
$this->
|
217 |
}
|
218 |
|
219 |
if ($this->charset) {
|
@@ -248,6 +316,7 @@ class Parser
|
|
248 |
$this->buffer = (string) $buffer;
|
249 |
|
250 |
$this->saveEncoding();
|
|
|
251 |
|
252 |
$list = $this->valueList($out);
|
253 |
|
@@ -263,10 +332,11 @@ class Parser
|
|
263 |
*
|
264 |
* @param string $buffer
|
265 |
* @param string|array $out
|
|
|
266 |
*
|
267 |
* @return boolean
|
268 |
*/
|
269 |
-
public function parseSelector($buffer, &$out)
|
270 |
{
|
271 |
$this->count = 0;
|
272 |
$this->env = null;
|
@@ -275,11 +345,21 @@ class Parser
|
|
275 |
$this->buffer = (string) $buffer;
|
276 |
|
277 |
$this->saveEncoding();
|
|
|
|
|
|
|
|
|
|
|
|
|
278 |
|
279 |
$selector = $this->selectors($out);
|
280 |
|
281 |
$this->restoreEncoding();
|
282 |
|
|
|
|
|
|
|
|
|
283 |
return $selector;
|
284 |
}
|
285 |
|
@@ -302,6 +382,7 @@ class Parser
|
|
302 |
$this->buffer = (string) $buffer;
|
303 |
|
304 |
$this->saveEncoding();
|
|
|
305 |
|
306 |
$isMediaQuery = $this->mediaQueryList($out);
|
307 |
|
@@ -446,6 +527,10 @@ class Parser
|
|
446 |
) {
|
447 |
! $this->cssOnly || $this->assertPlainCssValid(false, $s);
|
448 |
|
|
|
|
|
|
|
|
|
449 |
$this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
|
450 |
|
451 |
return true;
|
@@ -460,7 +545,7 @@ class Parser
|
|
460 |
$this->end()
|
461 |
) {
|
462 |
if ($this->cssOnly) {
|
463 |
-
$this->assertPlainCssValid($importPath, $s);
|
464 |
$this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
|
465 |
return true;
|
466 |
}
|
@@ -478,7 +563,7 @@ class Parser
|
|
478 |
$this->end()
|
479 |
) {
|
480 |
if ($this->cssOnly) {
|
481 |
-
$this->assertPlainCssValid($importPath, $s);
|
482 |
$this->append([Type::T_COMMENT, rtrim(substr($this->buffer, $s, $this->count - $s))]);
|
483 |
return true;
|
484 |
}
|
@@ -523,32 +608,6 @@ class Parser
|
|
523 |
|
524 |
$this->seek($s);
|
525 |
|
526 |
-
if (
|
527 |
-
$this->literal('@break', 6) &&
|
528 |
-
$this->end()
|
529 |
-
) {
|
530 |
-
! $this->cssOnly || $this->assertPlainCssValid(false, $s);
|
531 |
-
|
532 |
-
$this->append([Type::T_BREAK], $s);
|
533 |
-
|
534 |
-
return true;
|
535 |
-
}
|
536 |
-
|
537 |
-
$this->seek($s);
|
538 |
-
|
539 |
-
if (
|
540 |
-
$this->literal('@continue', 9) &&
|
541 |
-
$this->end()
|
542 |
-
) {
|
543 |
-
! $this->cssOnly || $this->assertPlainCssValid(false, $s);
|
544 |
-
|
545 |
-
$this->append([Type::T_CONTINUE], $s);
|
546 |
-
|
547 |
-
return true;
|
548 |
-
}
|
549 |
-
|
550 |
-
$this->seek($s);
|
551 |
-
|
552 |
if (
|
553 |
$this->literal('@return', 7) &&
|
554 |
($this->valueList($retVal) || true) &&
|
@@ -723,8 +782,7 @@ class Parser
|
|
723 |
$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
|
724 |
} elseif (
|
725 |
$this->literal('if', 2) &&
|
726 |
-
$this->
|
727 |
-
$this->matchChar('{', false)
|
728 |
) {
|
729 |
$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
|
730 |
$else->cond = $cond;
|
@@ -824,7 +882,7 @@ class Parser
|
|
824 |
! \in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA])
|
825 |
) {
|
826 |
$plain = \trim(\substr($this->buffer, $s, $this->count - $s));
|
827 |
-
$this->
|
828 |
"Unknown directive `{$plain}` not allowed in `" . $this->env->type . "` block"
|
829 |
);
|
830 |
}
|
@@ -932,11 +990,6 @@ class Parser
|
|
932 |
|
933 |
$this->seek($s);
|
934 |
|
935 |
-
// misc
|
936 |
-
if ($this->literal('-->', 3)) {
|
937 |
-
return true;
|
938 |
-
}
|
939 |
-
|
940 |
// opening css block
|
941 |
if (
|
942 |
$this->selectors($selectors) &&
|
@@ -965,7 +1018,7 @@ class Parser
|
|
965 |
|
966 |
if ($this->valueList($value)) {
|
967 |
if (empty($this->env->parent)) {
|
968 |
-
$this->
|
969 |
}
|
970 |
|
971 |
$this->append([Type::T_ASSIGN, $name, $value], $s);
|
@@ -1024,10 +1077,7 @@ class Parser
|
|
1024 |
}
|
1025 |
|
1026 |
// extra stuff
|
1027 |
-
if (
|
1028 |
-
$this->matchChar(';') ||
|
1029 |
-
$this->literal('<!--', 4)
|
1030 |
-
) {
|
1031 |
return true;
|
1032 |
}
|
1033 |
|
@@ -1037,10 +1087,10 @@ class Parser
|
|
1037 |
/**
|
1038 |
* Push block onto parse tree
|
1039 |
*
|
1040 |
-
* @param array
|
1041 |
* @param integer $pos
|
1042 |
*
|
1043 |
-
* @return
|
1044 |
*/
|
1045 |
protected function pushBlock($selectors, $pos = 0)
|
1046 |
{
|
@@ -1086,7 +1136,7 @@ class Parser
|
|
1086 |
* @param string $type
|
1087 |
* @param integer $pos
|
1088 |
*
|
1089 |
-
* @return
|
1090 |
*/
|
1091 |
protected function pushSpecialBlock($type, $pos)
|
1092 |
{
|
@@ -1099,7 +1149,7 @@ class Parser
|
|
1099 |
/**
|
1100 |
* Pop scope and return last block
|
1101 |
*
|
1102 |
-
* @return
|
1103 |
*
|
1104 |
* @throws \Exception
|
1105 |
*/
|
@@ -1115,7 +1165,7 @@ class Parser
|
|
1115 |
$block = $this->env;
|
1116 |
|
1117 |
if (empty($block->parent)) {
|
1118 |
-
$this->
|
1119 |
}
|
1120 |
|
1121 |
if ($block->type == Type::T_AT_ROOT) {
|
@@ -1146,7 +1196,7 @@ class Parser
|
|
1146 |
}
|
1147 |
|
1148 |
$r = '/' . $regex . '/' . $this->patternModifiers;
|
1149 |
-
$result = preg_match($r, $this->buffer, $out,
|
1150 |
|
1151 |
return $result;
|
1152 |
}
|
@@ -1164,7 +1214,7 @@ class Parser
|
|
1164 |
/**
|
1165 |
* Assert a parsed part is plain CSS Valid
|
1166 |
*
|
1167 |
-
* @param array $parsed
|
1168 |
* @param int $startPos
|
1169 |
* @throws ParserException
|
1170 |
*/
|
@@ -1185,7 +1235,7 @@ class Parser
|
|
1185 |
if ($type) {
|
1186 |
$message .= " ($type)";
|
1187 |
}
|
1188 |
-
$this->
|
1189 |
}
|
1190 |
|
1191 |
return $parsed;
|
@@ -1198,6 +1248,11 @@ class Parser
|
|
1198 |
*/
|
1199 |
protected function isPlainCssValidElement($parsed, $allowExpression = false)
|
1200 |
{
|
|
|
|
|
|
|
|
|
|
|
1201 |
if (
|
1202 |
\in_array($parsed[0], [Type::T_FUNCTION, Type::T_FUNCTION_CALL]) &&
|
1203 |
!\in_array($parsed[1], [
|
@@ -1209,6 +1264,7 @@ class Parser
|
|
1209 |
'grayscale',
|
1210 |
'hsl',
|
1211 |
'hsla',
|
|
|
1212 |
'invert',
|
1213 |
'linear-gradient',
|
1214 |
'min',
|
@@ -1232,6 +1288,7 @@ class Parser
|
|
1232 |
case Type::T_KEYWORD:
|
1233 |
case Type::T_NULL:
|
1234 |
case Type::T_NUMBER:
|
|
|
1235 |
return $parsed;
|
1236 |
|
1237 |
case Type::T_COMMENT:
|
@@ -1250,6 +1307,16 @@ class Parser
|
|
1250 |
|
1251 |
return $parsed;
|
1252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1253 |
case Type::T_STRING:
|
1254 |
foreach ($parsed[2] as $k => $substr) {
|
1255 |
if (\is_array($substr)) {
|
@@ -1261,6 +1328,18 @@ class Parser
|
|
1261 |
}
|
1262 |
return $parsed;
|
1263 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1264 |
case Type::T_ASSIGN:
|
1265 |
foreach ([1, 2, 3] as $k) {
|
1266 |
if (! empty($parsed[$k])) {
|
@@ -1297,6 +1376,7 @@ class Parser
|
|
1297 |
]
|
1298 |
];
|
1299 |
|
|
|
1300 |
case Type::T_UNARY:
|
1301 |
$parsed[2] = $this->isPlainCssValidElement($parsed[2]);
|
1302 |
if (! $parsed[2]) {
|
@@ -1389,7 +1469,7 @@ class Parser
|
|
1389 |
{
|
1390 |
$r = '/' . $regex . '/' . $this->patternModifiers;
|
1391 |
|
1392 |
-
if (! preg_match($r, $this->buffer, $out,
|
1393 |
return false;
|
1394 |
}
|
1395 |
|
@@ -1470,7 +1550,7 @@ class Parser
|
|
1470 |
{
|
1471 |
$gotWhite = false;
|
1472 |
|
1473 |
-
while (preg_match(static::$whitePattern, $this->buffer, $m,
|
1474 |
if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
|
1475 |
// comment that are kept in the output CSS
|
1476 |
$comment = [];
|
@@ -1498,6 +1578,9 @@ class Parser
|
|
1498 |
|
1499 |
$comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
|
1500 |
} else {
|
|
|
|
|
|
|
1501 |
$comment[] = substr($this->buffer, $this->count, 2);
|
1502 |
|
1503 |
$this->count += 2;
|
@@ -1515,7 +1598,14 @@ class Parser
|
|
1515 |
} else {
|
1516 |
$comment[] = $c;
|
1517 |
$staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
|
1518 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1519 |
}
|
1520 |
|
1521 |
$this->commentsSeen[$startCommentCount] = true;
|
@@ -1550,7 +1640,7 @@ class Parser
|
|
1550 |
/**
|
1551 |
* Append statement to current block
|
1552 |
*
|
1553 |
-
* @param array
|
1554 |
* @param integer $pos
|
1555 |
*/
|
1556 |
protected function append($statement, $pos = null)
|
@@ -1885,7 +1975,7 @@ class Parser
|
|
1885 |
|
1886 |
/**
|
1887 |
* Check if a generic directive is known to be able to allow almost any syntax or not
|
1888 |
-
* @param $directiveName
|
1889 |
* @return bool
|
1890 |
*/
|
1891 |
protected function isKnownGenericDirective($directiveName)
|
@@ -2063,10 +2153,10 @@ class Parser
|
|
2063 |
/**
|
2064 |
* Parse generic list
|
2065 |
*
|
2066 |
-
* @param array
|
2067 |
-
* @param
|
2068 |
-
* @param string
|
2069 |
-
* @param boolean
|
2070 |
*
|
2071 |
* @return boolean
|
2072 |
*/
|
@@ -2755,7 +2845,7 @@ class Parser
|
|
2755 |
$sss = $this->count;
|
2756 |
|
2757 |
if (! $this->matchChar(')')) {
|
2758 |
-
$this->
|
2759 |
}
|
2760 |
|
2761 |
$arg[2] = true;
|
@@ -2917,7 +3007,7 @@ class Parser
|
|
2917 |
$content[] = '#{'; // ignore it
|
2918 |
}
|
2919 |
} elseif ($m[2] === "\r") {
|
2920 |
-
$content[] =
|
2921 |
// TODO : warning
|
2922 |
# DEPRECATION WARNING on line x, column y of zzz:
|
2923 |
# Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
|
@@ -2936,7 +3026,7 @@ class Parser
|
|
2936 |
} elseif ($this->matchEscapeCharacter($c)) {
|
2937 |
$content[] = $c;
|
2938 |
} else {
|
2939 |
-
$this->
|
2940 |
}
|
2941 |
} else {
|
2942 |
$this->count -= \strlen($delim);
|
@@ -2961,8 +3051,14 @@ class Parser
|
|
2961 |
return false;
|
2962 |
}
|
2963 |
|
2964 |
-
|
|
|
|
|
|
|
|
|
|
|
2965 |
{
|
|
|
2966 |
if ($this->match('[a-f0-9]', $m, false)) {
|
2967 |
$hex = $m[0];
|
2968 |
|
@@ -2974,10 +3070,16 @@ class Parser
|
|
2974 |
}
|
2975 |
}
|
2976 |
|
|
|
|
|
|
|
|
|
2977 |
$value = hexdec($hex);
|
2978 |
|
2979 |
-
if ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF) {
|
2980 |
-
$out = "\u{FFFD}"
|
|
|
|
|
2981 |
} else {
|
2982 |
$out = Util::mbChr($value);
|
2983 |
}
|
@@ -2986,6 +3088,10 @@ class Parser
|
|
2986 |
}
|
2987 |
|
2988 |
if ($this->match('.', $m, false)) {
|
|
|
|
|
|
|
|
|
2989 |
$out = $m[0];
|
2990 |
|
2991 |
return true;
|
@@ -3217,7 +3323,7 @@ class Parser
|
|
3217 |
}
|
3218 |
|
3219 |
// match comment hack
|
3220 |
-
if (preg_match(static::$whitePattern, $this->buffer, $m,
|
3221 |
if (! empty($m[0])) {
|
3222 |
$parts[] = $m[0];
|
3223 |
$this->count += \strlen($m[0]);
|
@@ -3293,8 +3399,8 @@ class Parser
|
|
3293 |
/**
|
3294 |
* Parse comma separated selector list
|
3295 |
*
|
3296 |
-
* @param array
|
3297 |
-
* @param boolean $subSelector
|
3298 |
*
|
3299 |
* @return boolean
|
3300 |
*/
|
@@ -3329,8 +3435,8 @@ class Parser
|
|
3329 |
/**
|
3330 |
* Parse whitespace separated selector list
|
3331 |
*
|
3332 |
-
* @param array
|
3333 |
-
* @param boolean $subSelector
|
3334 |
*
|
3335 |
* @return boolean
|
3336 |
*/
|
@@ -3338,6 +3444,9 @@ class Parser
|
|
3338 |
{
|
3339 |
$selector = [];
|
3340 |
|
|
|
|
|
|
|
3341 |
for (;;) {
|
3342 |
$s = $this->count;
|
3343 |
|
@@ -3355,18 +3464,15 @@ class Parser
|
|
3355 |
|
3356 |
if ($this->selectorSingle($part, $subSelector)) {
|
3357 |
$selector[] = $part;
|
3358 |
-
$this->
|
3359 |
-
continue;
|
3360 |
-
}
|
3361 |
-
|
3362 |
-
if ($this->match('\/[^\/]+\/', $m, true)) {
|
3363 |
-
$selector[] = [$m[0]];
|
3364 |
continue;
|
3365 |
}
|
3366 |
|
3367 |
break;
|
3368 |
}
|
3369 |
|
|
|
|
|
3370 |
if (! $selector) {
|
3371 |
return false;
|
3372 |
}
|
@@ -3376,6 +3482,55 @@ class Parser
|
|
3376 |
return true;
|
3377 |
}
|
3378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3379 |
/**
|
3380 |
* Parse the parts that make up a selector
|
3381 |
*
|
@@ -3383,8 +3538,8 @@ class Parser
|
|
3383 |
* div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
|
3384 |
* }}
|
3385 |
*
|
3386 |
-
* @param array
|
3387 |
-
* @param boolean $subSelector
|
3388 |
*
|
3389 |
* @return boolean
|
3390 |
*/
|
@@ -3436,9 +3591,14 @@ class Parser
|
|
3436 |
continue 2;
|
3437 |
}
|
3438 |
|
3439 |
-
|
3440 |
-
|
3441 |
-
|
|
|
|
|
|
|
|
|
|
|
3442 |
}
|
3443 |
|
3444 |
if ($char === '%') {
|
@@ -3524,7 +3684,7 @@ class Parser
|
|
3524 |
$this->seek($ss);
|
3525 |
}
|
3526 |
} elseif (
|
3527 |
-
$this->matchChar('(') &&
|
3528 |
($this->openString(')', $str, '(') || true) &&
|
3529 |
$this->matchChar(')')
|
3530 |
) {
|
@@ -3581,7 +3741,7 @@ class Parser
|
|
3581 |
continue;
|
3582 |
}
|
3583 |
|
3584 |
-
if ($this->restrictedKeyword($name)) {
|
3585 |
$parts[] = $name;
|
3586 |
continue;
|
3587 |
}
|
@@ -3634,22 +3794,62 @@ class Parser
|
|
3634 |
*
|
3635 |
* @param string $word
|
3636 |
* @param boolean $eatWhitespace
|
|
|
3637 |
*
|
3638 |
* @return boolean
|
3639 |
*/
|
3640 |
-
protected function keyword(&$word, $eatWhitespace = null)
|
3641 |
{
|
|
|
3642 |
$match = $this->match(
|
3643 |
$this->utf8
|
3644 |
-
? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']
|
3645 |
-
: '(([\w_\-\*!"\']
|
3646 |
$m,
|
3647 |
-
|
3648 |
);
|
3649 |
|
3650 |
if ($match) {
|
3651 |
$word = $m[1];
|
3652 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3653 |
return true;
|
3654 |
}
|
3655 |
|
@@ -3661,14 +3861,15 @@ class Parser
|
|
3661 |
*
|
3662 |
* @param string $word
|
3663 |
* @param boolean $eatWhitespace
|
|
|
3664 |
*
|
3665 |
* @return boolean
|
3666 |
*/
|
3667 |
-
protected function restrictedKeyword(&$word, $eatWhitespace = null)
|
3668 |
{
|
3669 |
$s = $this->count;
|
3670 |
|
3671 |
-
if ($this->keyword($word, $eatWhitespace) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
|
3672 |
return true;
|
3673 |
}
|
3674 |
|
@@ -3893,11 +4094,20 @@ class Parser
|
|
3893 |
}
|
3894 |
|
3895 |
/**
|
3896 |
-
* Save internal encoding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3897 |
*/
|
3898 |
private function saveEncoding()
|
3899 |
{
|
3900 |
-
if (\extension_loaded('mbstring')) {
|
3901 |
$this->encoding = mb_internal_encoding();
|
3902 |
|
3903 |
mb_internal_encoding('iso-8859-1');
|
@@ -3906,6 +4116,8 @@ class Parser
|
|
3906 |
|
3907 |
/**
|
3908 |
* Restore internal encoding
|
|
|
|
|
3909 |
*/
|
3910 |
private function restoreEncoding()
|
3911 |
{
|
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 |
|
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();
|
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 |
{
|
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) {
|
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 |
|
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 |
|
536 |
return true;
|
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 |
}
|
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 |
}
|
608 |
|
609 |
$this->seek($s);
|
610 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
611 |
if (
|
612 |
$this->literal('@return', 7) &&
|
613 |
($this->valueList($retVal) || true) &&
|
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;
|
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 |
}
|
990 |
|
991 |
$this->seek($s);
|
992 |
|
|
|
|
|
|
|
|
|
|
|
993 |
// opening css block
|
994 |
if (
|
995 |
$this->selectors($selectors) &&
|
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);
|
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 |
{
|
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 |
}
|
1214 |
/**
|
1215 |
* Assert a parsed part is plain CSS Valid
|
1216 |
*
|
1217 |
+
* @param array|false $parsed
|
1218 |
* @param int $startPos
|
1219 |
* @throws ParserException
|
1220 |
*/
|
1235 |
if ($type) {
|
1236 |
$message .= " ($type)";
|
1237 |
}
|
1238 |
+
throw $this->parseError($message);
|
1239 |
}
|
1240 |
|
1241 |
return $parsed;
|
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], [
|
1264 |
'grayscale',
|
1265 |
'hsl',
|
1266 |
'hsla',
|
1267 |
+
'hwb',
|
1268 |
'invert',
|
1269 |
'linear-gradient',
|
1270 |
'min',
|
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:
|
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)) {
|
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])) {
|
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]) {
|
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 = [];
|
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 |
+
$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);
|
1584 |
$comment[] = substr($this->buffer, $this->count, 2);
|
1585 |
|
1586 |
$this->count += 2;
|
1598 |
} else {
|
1599 |
$comment[] = $c;
|
1600 |
$staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
|
1601 |
+
$commentStatement = [Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]];
|
1602 |
+
|
1603 |
+
list($line, $column) = $this->getSourcePosition($startCommentCount);
|
1604 |
+
$commentStatement[self::SOURCE_LINE] = $line;
|
1605 |
+
$commentStatement[self::SOURCE_COLUMN] = $column;
|
1606 |
+
$commentStatement[self::SOURCE_INDEX] = $this->sourceIndex;
|
1607 |
+
|
1608 |
+
$this->appendComment($commentStatement);
|
1609 |
}
|
1610 |
|
1611 |
$this->commentsSeen[$startCommentCount] = true;
|
1640 |
/**
|
1641 |
* Append statement to current block
|
1642 |
*
|
1643 |
+
* @param array|null $statement
|
1644 |
* @param integer $pos
|
1645 |
*/
|
1646 |
protected function append($statement, $pos = null)
|
1975 |
|
1976 |
/**
|
1977 |
* Check if a generic directive is known to be able to allow almost any syntax or not
|
1978 |
+
* @param mixed $directiveName
|
1979 |
* @return bool
|
1980 |
*/
|
1981 |
protected function isKnownGenericDirective($directiveName)
|
2153 |
/**
|
2154 |
* Parse generic list
|
2155 |
*
|
2156 |
+
* @param array $out
|
2157 |
+
* @param string $parseItem The name of the method used to parse items
|
2158 |
+
* @param string $delim
|
2159 |
+
* @param boolean $flatten
|
2160 |
*
|
2161 |
* @return boolean
|
2162 |
*/
|
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;
|
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.
|
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);
|
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 |
|
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 |
}
|
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;
|
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]);
|
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 |
|
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 |
*/
|
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 === '%') {
|
3684 |
$this->seek($ss);
|
3685 |
}
|
3686 |
} elseif (
|
3687 |
+
$this->matchChar('(', true) &&
|
3688 |
($this->openString(')', $str, '(') || true) &&
|
3689 |
$this->matchChar(')')
|
3690 |
) {
|
3741 |
continue;
|
3742 |
}
|
3743 |
|
3744 |
+
if ($this->restrictedKeyword($name, false, true)) {
|
3745 |
$parts[] = $name;
|
3746 |
continue;
|
3747 |
}
|
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 |
|
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 |
{
|
scssphp/src/SourceMap/Base64.php
CHANGED
@@ -16,11 +16,13 @@ namespace ScssPhp\ScssPhp\SourceMap;
|
|
16 |
* Base 64 Encode/Decode
|
17 |
*
|
18 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
19 |
*/
|
20 |
class Base64
|
21 |
{
|
22 |
/**
|
23 |
-
* @var array
|
24 |
*/
|
25 |
private static $encodingMap = [
|
26 |
0 => 'A',
|
@@ -90,7 +92,7 @@ class Base64
|
|
90 |
];
|
91 |
|
92 |
/**
|
93 |
-
* @var array
|
94 |
*/
|
95 |
private static $decodingMap = [
|
96 |
'A' => 0,
|
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,
|
scssphp/src/SourceMap/Base64VLQ.php
CHANGED
@@ -12,8 +12,6 @@
|
|
12 |
|
13 |
namespace ScssPhp\ScssPhp\SourceMap;
|
14 |
|
15 |
-
use ScssPhp\ScssPhp\SourceMap\Base64;
|
16 |
-
|
17 |
/**
|
18 |
* Base 64 VLQ
|
19 |
*
|
@@ -36,6 +34,8 @@ use ScssPhp\ScssPhp\SourceMap\Base64;
|
|
36 |
*
|
37 |
* @author John Lenz <johnlenz@google.com>
|
38 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
39 |
*/
|
40 |
class Base64VLQ
|
41 |
{
|
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 |
{
|
scssphp/src/SourceMap/Base64VLQEncoder.php
DELETED
@@ -1,217 +0,0 @@
|
|
1 |
-
<?php
|
2 |
-
/**
|
3 |
-
* SCSSPHP
|
4 |
-
*
|
5 |
-
* @copyright 2012-2019 Leaf Corcoran
|
6 |
-
*
|
7 |
-
* @license http://opensource.org/licenses/MIT MIT
|
8 |
-
*
|
9 |
-
* @link http://scssphp.github.io/scssphp
|
10 |
-
*/
|
11 |
-
|
12 |
-
namespace ScssPhp\ScssPhp\SourceMap;
|
13 |
-
|
14 |
-
/**
|
15 |
-
* Base64 VLQ Encoder
|
16 |
-
*
|
17 |
-
* {@internal Derivative of oyejorge/less.php's lib/SourceMap/Base64VLQ.php, relicensed with permission. }}
|
18 |
-
*
|
19 |
-
* @author Josh Schmidt <oyejorge@gmail.com>
|
20 |
-
* @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
|
21 |
-
*/
|
22 |
-
class Base64VLQEncoder
|
23 |
-
{
|
24 |
-
/**
|
25 |
-
* Shift
|
26 |
-
*
|
27 |
-
* @var integer
|
28 |
-
*/
|
29 |
-
private $shift = 5;
|
30 |
-
|
31 |
-
/**
|
32 |
-
* Mask
|
33 |
-
*
|
34 |
-
* @var integer
|
35 |
-
*/
|
36 |
-
private $mask = 0x1F; // == (1 << shift) == 0b00011111
|
37 |
-
|
38 |
-
/**
|
39 |
-
* Continuation bit
|
40 |
-
*
|
41 |
-
* @var integer
|
42 |
-
*/
|
43 |
-
private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
|
44 |
-
|
45 |
-
/**
|
46 |
-
* Char to integer map
|
47 |
-
*
|
48 |
-
* @var array
|
49 |
-
*/
|
50 |
-
private $charToIntMap = [
|
51 |
-
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7,
|
52 |
-
'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15,
|
53 |
-
'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23,
|
54 |
-
'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31,
|
55 |
-
'g' => 32, 'h' => 33, 'i' => 34, 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39,
|
56 |
-
'o' => 40, 'p' => 41, 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47,
|
57 |
-
'w' => 48, 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55,
|
58 |
-
4 => 56, 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
|
59 |
-
];
|
60 |
-
|
61 |
-
/**
|
62 |
-
* Integer to char map
|
63 |
-
*
|
64 |
-
* @var array
|
65 |
-
*/
|
66 |
-
private $intToCharMap = [
|
67 |
-
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H',
|
68 |
-
8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P',
|
69 |
-
16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X',
|
70 |
-
24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f',
|
71 |
-
32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n',
|
72 |
-
40 => 'o', 41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v',
|
73 |
-
48 => 'w', 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
|
74 |
-
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', 63 => '/',
|
75 |
-
];
|
76 |
-
|
77 |
-
/**
|
78 |
-
* Constructor
|
79 |
-
*/
|
80 |
-
public function __construct()
|
81 |
-
{
|
82 |
-
// I leave it here for future reference
|
83 |
-
// foreach (str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
|
84 |
-
// {
|
85 |
-
// $this->charToIntMap[$char] = $i;
|
86 |
-
// $this->intToCharMap[$i] = $char;
|
87 |
-
// }
|
88 |
-
}
|
89 |
-
|
90 |
-
/**
|
91 |
-
* Convert from a two-complement value to a value where the sign bit is
|
92 |
-
* is placed in the least significant bit. For example, as decimals:
|
93 |
-
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
94 |
-
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
95 |
-
* We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
|
96 |
-
* even on a 64 bit machine.
|
97 |
-
*
|
98 |
-
* @param string $aValue
|
99 |
-
*/
|
100 |
-
public function toVLQSigned($aValue)
|
101 |
-
{
|
102 |
-
return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
|
103 |
-
}
|
104 |
-
|
105 |
-
/**
|
106 |
-
* Convert to a two-complement value from a value where the sign bit is
|
107 |
-
* is placed in the least significant bit. For example, as decimals:
|
108 |
-
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
109 |
-
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
110 |
-
* We assume that the value was generated with a 32 bit machine in mind.
|
111 |
-
* Hence
|
112 |
-
* 1 becomes -2147483648
|
113 |
-
* even on a 64 bit machine.
|
114 |
-
*
|
115 |
-
* @param integer $aValue
|
116 |
-
*/
|
117 |
-
public function fromVLQSigned($aValue)
|
118 |
-
{
|
119 |
-
return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
|
120 |
-
}
|
121 |
-
|
122 |
-
/**
|
123 |
-
* Return the base 64 VLQ encoded value.
|
124 |
-
*
|
125 |
-
* @param string $aValue The value to encode
|
126 |
-
*
|
127 |
-
* @return string The encoded value
|
128 |
-
*/
|
129 |
-
public function encode($aValue)
|
130 |
-
{
|
131 |
-
$encoded = '';
|
132 |
-
$vlq = $this->toVLQSigned($aValue);
|
133 |
-
|
134 |
-
do {
|
135 |
-
$digit = $vlq & $this->mask;
|
136 |
-
$vlq = $this->zeroFill($vlq, $this->shift);
|
137 |
-
|
138 |
-
if ($vlq > 0) {
|
139 |
-
$digit |= $this->continuationBit;
|
140 |
-
}
|
141 |
-
|
142 |
-
$encoded .= $this->base64Encode($digit);
|
143 |
-
} while ($vlq > 0);
|
144 |
-
|
145 |
-
return $encoded;
|
146 |
-
}
|
147 |
-
|
148 |
-
/**
|
149 |
-
* Return the value decoded from base 64 VLQ.
|
150 |
-
*
|
151 |
-
* @param string $encoded The encoded value to decode
|
152 |
-
*
|
153 |
-
* @return integer The decoded value
|
154 |
-
*/
|
155 |
-
public function decode($encoded)
|
156 |
-
{
|
157 |
-
$vlq = 0;
|
158 |
-
$i = 0;
|
159 |
-
|
160 |
-
do {
|
161 |
-
$digit = $this->base64Decode($encoded[$i]);
|
162 |
-
$vlq |= ($digit & $this->mask) << ($i * $this->shift);
|
163 |
-
$i++;
|
164 |
-
} while ($digit & $this->continuationBit);
|
165 |
-
|
166 |
-
return $this->fromVLQSigned($vlq);
|
167 |
-
}
|
168 |
-
|
169 |
-
/**
|
170 |
-
* Right shift with zero fill.
|
171 |
-
*
|
172 |
-
* @param integer $a number to shift
|
173 |
-
* @param integer $b number of bits to shift
|
174 |
-
*
|
175 |
-
* @return integer
|
176 |
-
*/
|
177 |
-
public function zeroFill($a, $b)
|
178 |
-
{
|
179 |
-
return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
|
180 |
-
}
|
181 |
-
|
182 |
-
/**
|
183 |
-
* Encode single 6-bit digit as base64.
|
184 |
-
*
|
185 |
-
* @param integer $number
|
186 |
-
*
|
187 |
-
* @return string
|
188 |
-
*
|
189 |
-
* @throws \Exception If the number is invalid
|
190 |
-
*/
|
191 |
-
public function base64Encode($number)
|
192 |
-
{
|
193 |
-
if ($number < 0 || $number > 63) {
|
194 |
-
throw new \Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
|
195 |
-
}
|
196 |
-
|
197 |
-
return $this->intToCharMap[$number];
|
198 |
-
}
|
199 |
-
|
200 |
-
/**
|
201 |
-
* Decode single 6-bit digit from base64
|
202 |
-
*
|
203 |
-
* @param string $char
|
204 |
-
*
|
205 |
-
* @return integer
|
206 |
-
*
|
207 |
-
* @throws \Exception If the number is invalid
|
208 |
-
*/
|
209 |
-
public function base64Decode($char)
|
210 |
-
{
|
211 |
-
if (! array_key_exists($char, $this->charToIntMap)) {
|
212 |
-
throw new \Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
|
213 |
-
}
|
214 |
-
|
215 |
-
return $this->charToIntMap[$char];
|
216 |
-
}
|
217 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scssphp/src/SourceMap/SourceMapGenerator.php
CHANGED
@@ -21,6 +21,8 @@ use ScssPhp\ScssPhp\Exception\CompilerException;
|
|
21 |
*
|
22 |
* @author Josh Schmidt <oyejorge@gmail.com>
|
23 |
* @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
|
|
|
|
|
24 |
*/
|
25 |
class SourceMapGenerator
|
26 |
{
|
@@ -33,6 +35,7 @@ class SourceMapGenerator
|
|
33 |
* Array of default options
|
34 |
*
|
35 |
* @var array
|
|
|
36 |
*/
|
37 |
protected $defaultOptions = [
|
38 |
// an optional source root, useful for relocating source files
|
@@ -70,6 +73,7 @@ class SourceMapGenerator
|
|
70 |
* Array of mappings
|
71 |
*
|
72 |
* @var array
|
|
|
73 |
*/
|
74 |
protected $mappings = [];
|
75 |
|
@@ -83,16 +87,24 @@ class SourceMapGenerator
|
|
83 |
/**
|
84 |
* File to content map
|
85 |
*
|
86 |
-
* @var array
|
87 |
*/
|
88 |
protected $sources = [];
|
|
|
|
|
|
|
|
|
89 |
protected $sourceKeys = [];
|
90 |
|
91 |
/**
|
92 |
* @var array
|
|
|
93 |
*/
|
94 |
private $options;
|
95 |
|
|
|
|
|
|
|
96 |
public function __construct(array $options = [])
|
97 |
{
|
98 |
$this->options = array_merge($this->defaultOptions, $options);
|
@@ -107,6 +119,8 @@ class SourceMapGenerator
|
|
107 |
* @param integer $originalLine The line number in original file
|
108 |
* @param integer $originalColumn The column number in original file
|
109 |
* @param string $sourceFile The original source file
|
|
|
|
|
110 |
*/
|
111 |
public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
|
112 |
{
|
@@ -129,6 +143,7 @@ class SourceMapGenerator
|
|
129 |
* @return string
|
130 |
*
|
131 |
* @throws \ScssPhp\ScssPhp\Exception\CompilerException If the file could not be saved
|
|
|
132 |
*/
|
133 |
public function saveMap($content)
|
134 |
{
|
@@ -154,14 +169,16 @@ class SourceMapGenerator
|
|
154 |
/**
|
155 |
* Generates the JSON source map
|
156 |
*
|
|
|
|
|
157 |
* @return string
|
158 |
*
|
159 |
* @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
|
160 |
*/
|
161 |
-
public function generateJson()
|
162 |
{
|
163 |
$sourceMap = [];
|
164 |
-
$mappings = $this->generateMappings();
|
165 |
|
166 |
// File version (always the first entry in the object) and must be a positive integer.
|
167 |
$sourceMap['version'] = self::VERSION;
|
@@ -212,7 +229,7 @@ class SourceMapGenerator
|
|
212 |
/**
|
213 |
* Returns the sources contents
|
214 |
*
|
215 |
-
* @return
|
216 |
*/
|
217 |
protected function getSourcesContent()
|
218 |
{
|
@@ -232,14 +249,21 @@ class SourceMapGenerator
|
|
232 |
/**
|
233 |
* Generates the mappings string
|
234 |
*
|
|
|
|
|
235 |
* @return string
|
236 |
*/
|
237 |
-
public function generateMappings()
|
238 |
{
|
239 |
if (! \count($this->mappings)) {
|
240 |
return '';
|
241 |
}
|
242 |
|
|
|
|
|
|
|
|
|
|
|
243 |
$this->sourceKeys = array_flip(array_keys($this->sources));
|
244 |
|
245 |
// group mappings by generated line number.
|
@@ -254,6 +278,12 @@ class SourceMapGenerator
|
|
254 |
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
|
255 |
|
256 |
foreach ($groupedMap as $lineNumber => $lineMap) {
|
|
|
|
|
|
|
|
|
|
|
|
|
257 |
while (++$lastGeneratedLine < $lineNumber) {
|
258 |
$groupedMapEncoded[] = ';';
|
259 |
}
|
@@ -262,8 +292,10 @@ class SourceMapGenerator
|
|
262 |
$lastGeneratedColumn = 0;
|
263 |
|
264 |
foreach ($lineMap as $m) {
|
265 |
-
$
|
266 |
-
|
|
|
|
|
267 |
|
268 |
// find the index
|
269 |
if ($m['source_file']) {
|
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']) {
|
scssphp/src/Type.php
CHANGED
@@ -22,11 +22,14 @@ class Type
|
|
22 |
const T_ASSIGN = 'assign';
|
23 |
const T_AT_ROOT = 'at-root';
|
24 |
const T_BLOCK = 'block';
|
|
|
25 |
const T_BREAK = 'break';
|
26 |
const T_CHARSET = 'charset';
|
27 |
const T_COLOR = 'color';
|
28 |
const T_COMMENT = 'comment';
|
|
|
29 |
const T_CONTINUE = 'continue';
|
|
|
30 |
const T_CONTROL = 'control';
|
31 |
const T_CUSTOM_PROPERTY = 'custom';
|
32 |
const T_DEBUG = 'debug';
|
@@ -42,6 +45,7 @@ class Type
|
|
42 |
const T_FUNCTION_REFERENCE = 'function-reference';
|
43 |
const T_FUNCTION_CALL = 'fncall';
|
44 |
const T_HSL = 'hsl';
|
|
|
45 |
const T_IF = 'if';
|
46 |
const T_IMPORT = 'import';
|
47 |
const T_INCLUDE = 'include';
|
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';
|
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';
|
scssphp/src/Util.php
CHANGED
@@ -14,11 +14,14 @@ namespace ScssPhp\ScssPhp;
|
|
14 |
|
15 |
use ScssPhp\ScssPhp\Base\Range;
|
16 |
use ScssPhp\ScssPhp\Exception\RangeException;
|
|
|
17 |
|
18 |
/**
|
19 |
-
*
|
20 |
*
|
21 |
* @author Anthon Pang <anthon.pang@gmail.com>
|
|
|
|
|
22 |
*/
|
23 |
class Util
|
24 |
{
|
@@ -26,10 +29,10 @@ class Util
|
|
26 |
* Asserts that `value` falls within `range` (inclusive), leaving
|
27 |
* room for slight floating-point errors.
|
28 |
*
|
29 |
-
* @param string
|
30 |
-
* @param
|
31 |
-
* @param array
|
32 |
-
* @param string
|
33 |
*
|
34 |
* @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin.
|
35 |
*
|
@@ -82,8 +85,8 @@ class Util
|
|
82 |
*/
|
83 |
public static function mbChr($code)
|
84 |
{
|
85 |
-
// Use the native implementation if available.
|
86 |
-
if (\function_exists('mb_chr')) {
|
87 |
return mb_chr($code, 'UTF-8');
|
88 |
}
|
89 |
|
@@ -105,7 +108,7 @@ class Util
|
|
105 |
* mb_strlen() wrapper
|
106 |
*
|
107 |
* @param string $string
|
108 |
-
* @return
|
109 |
*/
|
110 |
public static function mbStrlen($string)
|
111 |
{
|
@@ -115,10 +118,10 @@ class Util
|
|
115 |
}
|
116 |
|
117 |
if (\function_exists('iconv_strlen')) {
|
118 |
-
return @iconv_strlen($string, 'UTF-8');
|
119 |
}
|
120 |
|
121 |
-
|
122 |
}
|
123 |
|
124 |
/**
|
@@ -155,6 +158,27 @@ class Util
|
|
155 |
return (string)iconv_substr($string, $start, $length, 'UTF-8');
|
156 |
}
|
157 |
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
}
|
160 |
}
|
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 |
*
|
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 |
|
108 |
* mb_strlen() wrapper
|
109 |
*
|
110 |
* @param string $string
|
111 |
+
* @return int
|
112 |
*/
|
113 |
public static function mbStrlen($string)
|
114 |
{
|
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 |
/**
|
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 |
}
|
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 |
+
}
|
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 |
+
}
|
scssphp/src/Version.php
CHANGED
@@ -19,5 +19,5 @@ namespace ScssPhp\ScssPhp;
|
|
19 |
*/
|
20 |
class Version
|
21 |
{
|
22 |
-
const VERSION = '1.2
|
23 |
}
|
19 |
*/
|
20 |
class Version
|
21 |
{
|
22 |
+
const VERSION = '1.5.2';
|
23 |
}
|
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 |
+
}
|
wp-scss.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: WP-SCSS
|
4 |
* Plugin URI: https://github.com/ConnectThink/WP-SCSS
|
5 |
* Description: Compiles scss files live on WordPress.
|
6 |
-
* Version: 2.
|
7 |
* Author: Connect Think
|
8 |
* Author URI: http://connectthink.com
|
9 |
* License: GPLv3
|
@@ -44,7 +44,7 @@ if (!defined('WPSCSS_VERSION_KEY'))
|
|
44 |
define('WPSCSS_VERSION_KEY', 'wpscss_version');
|
45 |
|
46 |
if (!defined('WPSCSS_VERSION_NUM'))
|
47 |
-
define('WPSCSS_VERSION_NUM', '2.
|
48 |
|
49 |
// Add version to options table
|
50 |
if ( get_option( WPSCSS_VERSION_KEY ) !== false ) {
|
@@ -111,7 +111,12 @@ function wpscss_plugin_action_links($links, $file) {
|
|
111 |
|
112 |
add_filter('option_wpscss_options', 'wpscss_plugin_db_cleanup');
|
113 |
function wpscss_plugin_db_cleanup($option_values){
|
114 |
-
$
|
|
|
|
|
|
|
|
|
|
|
115 |
return $option_values;
|
116 |
}
|
117 |
|
@@ -161,7 +166,7 @@ if( $scss_dir_setting == false || $css_dir_setting == false ) {
|
|
161 |
$wpscss_settings = array(
|
162 |
'scss_dir' => $base_compiling_folder . $scss_dir_setting,
|
163 |
'css_dir' => $base_compiling_folder . $css_dir_setting,
|
164 |
-
'compiling' => isset($wpscss_options['compiling_options']) ? $wpscss_options['compiling_options'] : '
|
165 |
'always_recompile' => isset($wpscss_options['always_recompile']) ? $wpscss_options['always_recompile'] : false,
|
166 |
'errors' => isset($wpscss_options['errors']) ? $wpscss_options['errors'] : 'show',
|
167 |
'sourcemaps' => isset($wpscss_options['sourcemap_options']) ? $wpscss_options['sourcemap_options'] : 'SOURCE_MAP_NONE',
|
@@ -194,7 +199,6 @@ function wp_scss_needs_compiling() {
|
|
194 |
wpscss_handle_errors();
|
195 |
}
|
196 |
}
|
197 |
-
|
198 |
add_action('wp_loaded', 'wp_scss_needs_compiling');
|
199 |
|
200 |
function wp_scss_compile() {
|
3 |
* Plugin Name: WP-SCSS
|
4 |
* Plugin URI: https://github.com/ConnectThink/WP-SCSS
|
5 |
* Description: Compiles scss files live on WordPress.
|
6 |
+
* Version: 2.3.0
|
7 |
* Author: Connect Think
|
8 |
* Author URI: http://connectthink.com
|
9 |
* License: GPLv3
|
44 |
define('WPSCSS_VERSION_KEY', 'wpscss_version');
|
45 |
|
46 |
if (!defined('WPSCSS_VERSION_NUM'))
|
47 |
+
define('WPSCSS_VERSION_NUM', '2.3.0');
|
48 |
|
49 |
// Add version to options table
|
50 |
if ( get_option( WPSCSS_VERSION_KEY ) !== false ) {
|
111 |
|
112 |
add_filter('option_wpscss_options', 'wpscss_plugin_db_cleanup');
|
113 |
function wpscss_plugin_db_cleanup($option_values){
|
114 |
+
$compiling_options = str_replace("Leafo", "ScssPhp", $option_values['compiling_options']);
|
115 |
+
$compiling_options = str_replace("ScssPhp\\ScssPhp\\Formatter\\", "", $compiling_options);
|
116 |
+
$compiling_options = str_replace(["Compact", "Crunched"], "compressed", $compiling_options);
|
117 |
+
$compiling_options = str_replace("Nested", "expanded", $compiling_options);
|
118 |
+
$compiling_options = strtolower($compiling_options);
|
119 |
+
$option_values['compiling_options'] = $compiling_options;
|
120 |
return $option_values;
|
121 |
}
|
122 |
|
166 |
$wpscss_settings = array(
|
167 |
'scss_dir' => $base_compiling_folder . $scss_dir_setting,
|
168 |
'css_dir' => $base_compiling_folder . $css_dir_setting,
|
169 |
+
'compiling' => isset($wpscss_options['compiling_options']) ? $wpscss_options['compiling_options'] : 'compressed',
|
170 |
'always_recompile' => isset($wpscss_options['always_recompile']) ? $wpscss_options['always_recompile'] : false,
|
171 |
'errors' => isset($wpscss_options['errors']) ? $wpscss_options['errors'] : 'show',
|
172 |
'sourcemaps' => isset($wpscss_options['sourcemap_options']) ? $wpscss_options['sourcemap_options'] : 'SOURCE_MAP_NONE',
|
199 |
wpscss_handle_errors();
|
200 |
}
|
201 |
}
|
|
|
202 |
add_action('wp_loaded', 'wp_scss_needs_compiling');
|
203 |
|
204 |
function wp_scss_compile() {
|