feat(demo): add story 1 — Sorano: Rock and Time
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015 Leaf Corcoran, https://scssphp.github.io/scssphp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# scssphp
|
||||
### <https://scssphp.github.io/scssphp>
|
||||
|
||||

|
||||
[](https://packagist.org/packages/scssphp/scssphp)
|
||||
|
||||
`scssphp` is a compiler for SCSS written in PHP.
|
||||
|
||||
Checkout the homepage, <https://scssphp.github.io/scssphp>, for directions on how to use.
|
||||
|
||||
## Running Tests
|
||||
|
||||
`scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing.
|
||||
|
||||
Run the following command from the root directory to run every test:
|
||||
|
||||
vendor/bin/phpunit tests
|
||||
|
||||
There are several tests in the `tests/` directory:
|
||||
|
||||
* `ApiTest.php` contains various unit tests that test the PHP interface.
|
||||
* `ExceptionTest.php` contains unit tests that test for exceptions thrown by the parser and compiler.
|
||||
* `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs.
|
||||
* `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory
|
||||
then compares to the respective `.css` file in the `tests/outputs` directory.
|
||||
* `SassSpecTest.php` extracts tests from the `sass/sass-spec` repository.
|
||||
|
||||
When changing any of the tests in `tests/inputs`, the tests will most likely
|
||||
fail because the output has changed. Once you verify that the output is correct
|
||||
you can run the following command to rebuild all the tests:
|
||||
|
||||
BUILD=1 vendor/bin/phpunit tests
|
||||
|
||||
This will compile all the tests, and save results into `tests/outputs`. It also
|
||||
updates the list of excluded specs from sass-spec.
|
||||
|
||||
To enable the full `sass-spec` compatibility tests:
|
||||
|
||||
TEST_SASS_SPEC=1 vendor/bin/phpunit tests
|
||||
|
||||
## Coding Standard
|
||||
|
||||
`scssphp` source conforms to [PSR12](https://www.php-fig.org/psr/psr-12/).
|
||||
|
||||
Run the following command from the root directory to check the code for "sniffs".
|
||||
|
||||
vendor/bin/phpcs --standard=PSR12 --extensions=php bin src tests
|
||||
|
||||
## Static Analysis
|
||||
|
||||
`scssphp` uses [phpstan](https://phpstan.org/) for static analysis.
|
||||
|
||||
Run the following command from the root directory to analyse the codebase:
|
||||
|
||||
make phpstan
|
||||
|
||||
As most of the codebase is composed of legacy code which cannot be type-checked
|
||||
fully, the setup contains a baseline file with all errors we want to ignore. In
|
||||
particular, we ignore all errors related to not specifying the types inside arrays
|
||||
when these arrays correspond to the representation of Sass values and Sass AST nodes
|
||||
in the parser and compiler.
|
||||
When contributing, the proper process to deal with static analysis is the following:
|
||||
|
||||
1. Make your change in the codebase
|
||||
2. Run `make phpstan`
|
||||
3. Fix errors reported by phpstan when possible
|
||||
4. Repeat step 2 and 3 until nothing gets fixed anymore at step 3
|
||||
5. Run `make phpstan-baseline` to regenerate the phpstan baseline
|
||||
|
||||
Additions to the baseline will be reviewed to avoid ignoring errors that should have
|
||||
been fixed.
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.6') < 0) {
|
||||
die('Requires PHP 5.6 or above');
|
||||
}
|
||||
|
||||
include __DIR__ . '/../scss.inc.php';
|
||||
|
||||
use ScssPhp\ScssPhp\Compiler;
|
||||
use ScssPhp\ScssPhp\Exception\SassException;
|
||||
use ScssPhp\ScssPhp\OutputStyle;
|
||||
use ScssPhp\ScssPhp\Parser;
|
||||
use ScssPhp\ScssPhp\Version;
|
||||
|
||||
$style = null;
|
||||
$loadPaths = [];
|
||||
$dumpTree = false;
|
||||
$inputFile = null;
|
||||
$changeDir = false;
|
||||
$encoding = false;
|
||||
$sourceMap = false;
|
||||
$embedSources = false;
|
||||
$embedSourceMap = false;
|
||||
|
||||
/**
|
||||
* Parse argument
|
||||
*
|
||||
* @param int $i
|
||||
* @param string[] $options
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function parseArgument(&$i, $options) {
|
||||
global $argc;
|
||||
global $argv;
|
||||
|
||||
if (! preg_match('/^(?:' . implode('|', (array) $options) . ')=?(.*)/', $argv[$i], $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen($matches[1])) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
if ($i + 1 < $argc) {
|
||||
$i++;
|
||||
|
||||
return $argv[$i];
|
||||
}
|
||||
}
|
||||
|
||||
$arguments = [];
|
||||
|
||||
for ($i = 1; $i < $argc; $i++) {
|
||||
if ($argv[$i] === '-?' || $argv[$i] === '-h' || $argv[$i] === '--help') {
|
||||
$exe = $argv[0];
|
||||
|
||||
$HELP = <<<EOT
|
||||
Usage: $exe [options] [input-file] [output-file]
|
||||
|
||||
Options include:
|
||||
|
||||
--help Show this message [-h, -?]
|
||||
--continue-on-error [deprecated] Ignored
|
||||
--debug-info [deprecated] Ignored [-g]
|
||||
--dump-tree [deprecated] Dump formatted parse tree [-T]
|
||||
--iso8859-1 Use iso8859-1 encoding instead of default utf-8
|
||||
--line-numbers [deprecated] Ignored [--line-comments]
|
||||
--load-path=PATH Set import path [-I]
|
||||
--precision=N [deprecated] Ignored. (default 10) [-p]
|
||||
--sourcemap Create source map file
|
||||
--embed-sources Embed source file contents in source maps
|
||||
--embed-source-map Embed the source map contents in CSS (default if writing to stdout)
|
||||
--style=FORMAT Set the output style (compressed or expanded) [-s, -t]
|
||||
--version Print the version [-v]
|
||||
|
||||
EOT;
|
||||
exit($HELP);
|
||||
}
|
||||
|
||||
if ($argv[$i] === '-v' || $argv[$i] === '--version') {
|
||||
exit(Version::VERSION . "\n");
|
||||
}
|
||||
|
||||
// Keep parsing --continue-on-error to avoid BC breaks for scripts using it
|
||||
if ($argv[$i] === '--continue-on-error') {
|
||||
// TODO report it as a warning ?
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep parsing it to avoid BC breaks for scripts using it
|
||||
if ($argv[$i] === '-g' || $argv[$i] === '--debug-info') {
|
||||
// TODO report it as a warning ?
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--iso8859-1') {
|
||||
$encoding = 'iso8859-1';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep parsing it to avoid BC breaks for scripts using it
|
||||
if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
|
||||
// TODO report it as a warning ?
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--sourcemap') {
|
||||
$sourceMap = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--embed-sources') {
|
||||
$embedSources = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--embed-source-map') {
|
||||
$embedSourceMap = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '-T' || $argv[$i] === '--dump-tree') {
|
||||
$dumpTree = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = parseArgument($i, array('-t', '-s', '--style'));
|
||||
|
||||
if (isset($value)) {
|
||||
$style = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = parseArgument($i, array('-I', '--load-path'));
|
||||
|
||||
if (isset($value)) {
|
||||
$loadPaths[] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep parsing --precision to avoid BC breaks for scripts using it
|
||||
$value = parseArgument($i, array('-p', '--precision'));
|
||||
|
||||
if (isset($value)) {
|
||||
// TODO report it as a warning ?
|
||||
continue;
|
||||
}
|
||||
|
||||
$arguments[] = $argv[$i];
|
||||
}
|
||||
|
||||
|
||||
if (isset($arguments[0]) && file_exists($arguments[0])) {
|
||||
$inputFile = $arguments[0];
|
||||
$data = file_get_contents($inputFile);
|
||||
} else {
|
||||
$data = '';
|
||||
|
||||
while (! feof(STDIN)) {
|
||||
$data .= fread(STDIN, 8192);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dumpTree) {
|
||||
$parser = new Parser($inputFile);
|
||||
|
||||
print_r(json_decode(json_encode($parser->parse($data)), true));
|
||||
|
||||
fwrite(STDERR, 'Warning: the --dump-tree option is deprecated. Use proper debugging tools instead.');
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$scss = new Compiler();
|
||||
|
||||
if ($loadPaths) {
|
||||
$scss->setImportPaths($loadPaths);
|
||||
}
|
||||
|
||||
if ($style) {
|
||||
if ($style === OutputStyle::COMPRESSED || $style === OutputStyle::EXPANDED) {
|
||||
$scss->setOutputStyle($style);
|
||||
} else {
|
||||
fwrite(STDERR, "WARNING: the $style style is deprecated.\n");
|
||||
$scss->setFormatter('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
|
||||
}
|
||||
}
|
||||
|
||||
$outputFile = isset($arguments[1]) ? $arguments[1] : null;
|
||||
$sourceMapFile = null;
|
||||
|
||||
if ($sourceMap) {
|
||||
$sourceMapOptions = array(
|
||||
'outputSourceFiles' => $embedSources,
|
||||
);
|
||||
if ($embedSourceMap || $outputFile === null) {
|
||||
$scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
|
||||
} else {
|
||||
$sourceMapFile = $outputFile . '.map';
|
||||
$sourceMapOptions['sourceMapWriteTo'] = $sourceMapFile;
|
||||
$sourceMapOptions['sourceMapURL'] = basename($sourceMapFile);
|
||||
$sourceMapOptions['sourceMapBasepath'] = getcwd();
|
||||
$sourceMapOptions['sourceMapFilename'] = basename($outputFile);
|
||||
|
||||
$scss->setSourceMap(Compiler::SOURCE_MAP_FILE);
|
||||
}
|
||||
|
||||
$scss->setSourceMapOptions($sourceMapOptions);
|
||||
}
|
||||
|
||||
if ($encoding) {
|
||||
$scss->setEncoding($encoding);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $scss->compileString($data, $inputFile);
|
||||
} catch (SassException $e) {
|
||||
fwrite(STDERR, 'Error: '.$e->getMessage()."\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ($outputFile) {
|
||||
file_put_contents($outputFile, $result->getCss());
|
||||
|
||||
if ($sourceMapFile !== null && $result->getSourceMap() !== null) {
|
||||
file_put_contents($sourceMapFile, $result->getSourceMap());
|
||||
}
|
||||
} else {
|
||||
echo $result->getCss();
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"name": "scssphp/scssphp",
|
||||
"type": "library",
|
||||
"description": "scssphp is a compiler for SCSS written in PHP.",
|
||||
"keywords": ["css", "stylesheet", "scss", "sass", "less"],
|
||||
"homepage": "https://scssphp.github.io/scssphp/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Anthon Pang",
|
||||
"email": "apang@softwaredevelopment.ca",
|
||||
"homepage": "https://github.com/robocoder"
|
||||
},
|
||||
{
|
||||
"name": "Cédric Morin",
|
||||
"email": "cedric@yterium.com",
|
||||
"homepage": "https://github.com/Cerdic"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "ScssPhp\\ScssPhp\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "ScssPhp\\ScssPhp\\Tests\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"ext-ctype": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"league/uri": "^7.6",
|
||||
"league/uri-interfaces": "^7.6",
|
||||
"scssphp/source-span": "^1.1",
|
||||
"symfony/filesystem": "^5.4 || ^6.0 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"jgthms/bulma": "~0.9.4",
|
||||
"jiripudil/phpstan-sealed-classes": "^1.3",
|
||||
"phpstan/phpstan": "^2.1.31",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phpunit/phpunit": "^9.5.6",
|
||||
"sass/sass-spec": "*",
|
||||
"squizlabs/php_codesniffer": "^3.13",
|
||||
"symfony/phpunit-bridge": "^7.3 || ^8.0",
|
||||
"symfony/polyfill-php84": "^1.33",
|
||||
"symfony/var-dumper": "^6.4 || ^7.3 || ^8.0",
|
||||
"thoughtbot/bourbon": "^7.0",
|
||||
"twbs/bootstrap": "^5.3",
|
||||
"twbs/bootstrap4": "4.6.1",
|
||||
"zurb/foundation": "~6.7.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "sass/sass-spec",
|
||||
"version": "2024.06.24",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sass/sass-spec.git",
|
||||
"reference": "7ac806618da724333c60ad7b9c16b969470b9302"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sass/sass-spec/zipball/7ac806618da724333c60ad7b9c16b969470b9302",
|
||||
"reference": "7ac806618da724333c60ad7b9c16b969470b9302",
|
||||
"shasum": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "thoughtbot/bourbon",
|
||||
"version": "v7.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thoughtbot/bourbon.git",
|
||||
"reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thoughtbot/bourbon/zipball/fbe338ee6807e7f7aa996d82c8a16f248bb149b3",
|
||||
"reference": "fbe338ee6807e7f7aa996d82c8a16f248bb149b3",
|
||||
"shasum": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "jgthms/bulma",
|
||||
"version": "v0.9.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jgthms/bulma.git",
|
||||
"reference": "3e00a8e6d0d0e566d507328f0185ef84854effba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jgthms/bulma/zipball/3e00a8e6d0d0e566d507328f0185ef84854effba",
|
||||
"reference": "3e00a8e6d0d0e566d507328f0185ef84854effba",
|
||||
"shasum": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "twbs/bootstrap4",
|
||||
"version": "v4.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twbs/bootstrap.git",
|
||||
"reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twbs/bootstrap/zipball/043a03c95a2ad6738f85b65e53b9dbdfb03b8d10",
|
||||
"reference": "043a03c95a2ad6738f85b65e53b9dbdfb03b8d10",
|
||||
"shasum": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.6') < 0) {
|
||||
throw new \Exception('scssphp requires PHP 5.6 or above');
|
||||
}
|
||||
|
||||
if (! class_exists('ScssPhp\ScssPhp\Version')) {
|
||||
spl_autoload_register(function ($class) {
|
||||
if (0 !== strpos($class, 'ScssPhp\ScssPhp\\')) {
|
||||
// Not a ScssPhp class
|
||||
return;
|
||||
}
|
||||
|
||||
$subClass = substr($class, strlen('ScssPhp\ScssPhp\\'));
|
||||
$path = __DIR__ . '/src/' . str_replace('\\', '/', $subClass) . '.php';
|
||||
|
||||
if (file_exists($path)) {
|
||||
require $path;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast;
|
||||
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A node in an abstract syntax tree.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface AstNode extends \Stringable
|
||||
{
|
||||
public function getSpan(): FileSpan;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* An unknown plain CSS at-rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssAtRule extends CssParentNode
|
||||
{
|
||||
/**
|
||||
* The name of this rule.
|
||||
*
|
||||
* @return CssValue<string>
|
||||
*/
|
||||
public function getName(): CssValue;
|
||||
|
||||
/**
|
||||
* The value of this rule.
|
||||
*
|
||||
* @return CssValue<string>|null
|
||||
*/
|
||||
public function getValue(): ?CssValue;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A plain CSS comment.
|
||||
*
|
||||
* This is always a multi-line comment.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssComment extends CssNode
|
||||
{
|
||||
/**
|
||||
* The contents of this comment, including `/*` and `* /`.
|
||||
*/
|
||||
public function getText(): string;
|
||||
|
||||
/**
|
||||
* Whether this comment starts with `/*!` and so should be preserved even in
|
||||
* compressed mode.
|
||||
*/
|
||||
public function isPreserved(): bool;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\StackTrace\Trace;
|
||||
use ScssPhp\ScssPhp\Value\Value;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A plain CSS declaration (that is, a `name: value` pair).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssDeclaration extends CssNode
|
||||
{
|
||||
/**
|
||||
* The name of this declaration.
|
||||
*
|
||||
* @return CssValue<string>
|
||||
*/
|
||||
public function getName(): CssValue;
|
||||
|
||||
/**
|
||||
* The value of this declaration.
|
||||
*
|
||||
* @return CssValue<Value>
|
||||
*/
|
||||
public function getValue(): CssValue;
|
||||
|
||||
/**
|
||||
* A list of style rules that appeared before this declaration in the Sass
|
||||
* input but after it in the CSS output.
|
||||
*
|
||||
* These are used to emit mixed declaration deprecation warnings during
|
||||
* serialization, so we can check based on specificity whether the warnings
|
||||
* are really necessary without worrying about `@extend` potentially changing
|
||||
* things up.
|
||||
*
|
||||
* @return list<CssStyleRule>
|
||||
*/
|
||||
public function getInterleavedRules(): array;
|
||||
|
||||
/**
|
||||
* The stack trace indicating where this node was created.
|
||||
*
|
||||
* This is used to emit interleaved declaration warnings, and only needs to be set if
|
||||
* {@see getInterleavedRules} isn't empty.
|
||||
*/
|
||||
public function getTrace(): ?Trace;
|
||||
|
||||
/**
|
||||
* The span for {@see getValue} that should be emitted to the source map.
|
||||
*
|
||||
* When the declaration's expression is just a variable, this is the span
|
||||
* where that variable was declared whereas `$this->getValue()->getSpan()` is the span where
|
||||
* the variable was used. Otherwise, this is identical to `$this->getValue()->getSpan()`.
|
||||
*/
|
||||
public function getValueSpanForMap(): FileSpan;
|
||||
|
||||
/**
|
||||
* Returns whether this is a CSS Custom Property declaration.
|
||||
*/
|
||||
public function isCustomProperty(): bool;
|
||||
|
||||
/**
|
||||
* Whether this was originally parsed as a custom property declaration, as
|
||||
* opposed to using something like `#{--foo}: ...` to cause it to be parsed
|
||||
* as a normal Sass declaration.
|
||||
*
|
||||
* If this is `true`, {@see isCustomProperty} will also be `true` and {@see getValue} will
|
||||
* contain a {@see SassString}.
|
||||
*/
|
||||
public function isParsedAsCustomProperty(): bool;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A plain CSS `@import`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssImport extends CssNode
|
||||
{
|
||||
/**
|
||||
* The URL being imported.
|
||||
*
|
||||
* This includes quotes.
|
||||
*
|
||||
* @return CssValue<string>
|
||||
*/
|
||||
public function getUrl(): CssValue;
|
||||
|
||||
/**
|
||||
* The modifiers (such as media or supports queries) attached to this import.
|
||||
*
|
||||
* @return CssValue<string>|null
|
||||
*/
|
||||
public function getModifiers(): ?CssValue;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A block within a `@keyframes` rule.
|
||||
*
|
||||
* For example, `10% {opacity: 0.5}`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssKeyframeBlock extends CssParentNode
|
||||
{
|
||||
/**
|
||||
* The selector for this block.
|
||||
*
|
||||
* @return CssValue<list<string>>
|
||||
*/
|
||||
public function getSelector(): CssValue;
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use League\Uri\Contracts\UriInterface;
|
||||
use ScssPhp\ScssPhp\Exception\SassFormatException;
|
||||
use ScssPhp\ScssPhp\Logger\LoggerInterface;
|
||||
use ScssPhp\ScssPhp\Parser\InterpolationMap;
|
||||
use ScssPhp\ScssPhp\Parser\MediaQueryParser;
|
||||
use ScssPhp\ScssPhp\Util\Equatable;
|
||||
|
||||
/**
|
||||
* A plain CSS media query, as used in `@media` and `@import`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CssMediaQuery implements MediaQueryMergeResult, Equatable
|
||||
{
|
||||
/**
|
||||
* The modifier, probably either "not" or "only".
|
||||
*
|
||||
* This may be `null` if no modifier is in use.
|
||||
*/
|
||||
private readonly ?string $modifier;
|
||||
|
||||
/**
|
||||
* The media type, for example "screen" or "print".
|
||||
*
|
||||
* This may be `null`. If so, {@see $conditions} will not be empty.
|
||||
*/
|
||||
private readonly ?string $type;
|
||||
|
||||
/**
|
||||
* Whether {@see $conditions} is a conjunction or a disjunction.
|
||||
*
|
||||
* In other words, if this is `true` this query matches when _all_
|
||||
* {@see $conditions} are met, and if it's `false` this query matches when _any_
|
||||
* condition in {@see $conditions} is met.
|
||||
*
|
||||
* If this is `false`, {@see $modifier} and {@see $type} will both be `null`.
|
||||
*/
|
||||
private readonly bool $conjunction;
|
||||
|
||||
/**
|
||||
* Media conditions, including parentheses.
|
||||
*
|
||||
* This is anything that can appear in the [`<media-in-parens>`] production.
|
||||
*
|
||||
* [`<media-in-parens>`]: https://drafts.csswg.org/mediaqueries-4/#typedef-media-in-parens
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
private readonly array $conditions;
|
||||
|
||||
/**
|
||||
* Parses a media query from $contents.
|
||||
*
|
||||
* If passed, $url is the name of the file from which $contents comes.
|
||||
*
|
||||
* @return list<CssMediaQuery>
|
||||
*
|
||||
* @throws SassFormatException if parsing fails
|
||||
*/
|
||||
public static function parseList(string $contents, ?LoggerInterface $logger = null, ?UriInterface $url = null, ?InterpolationMap $interpolationMap = null): array
|
||||
{
|
||||
return (new MediaQueryParser($contents, $logger, $url, $interpolationMap))->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $conditions
|
||||
*/
|
||||
private function __construct(array $conditions = [], bool $conjunction = true, ?string $type = null, ?string $modifier = null)
|
||||
{
|
||||
$this->modifier = $modifier;
|
||||
$this->type = $type;
|
||||
$this->conditions = $conditions;
|
||||
$this->conjunction = $conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a media query specifies a type and, optionally, conditions.
|
||||
*
|
||||
* This always sets {@see $conjunction} to `true`.
|
||||
*
|
||||
* @param list<string> $conditions
|
||||
*/
|
||||
public static function type(?string $type, ?string $modifier = null, array $conditions = []): CssMediaQuery
|
||||
{
|
||||
return new CssMediaQuery($conditions, true, $type, $modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a media query that matches $conditions according to
|
||||
* $conjunction.
|
||||
*
|
||||
* The $conjunction argument may not be null if $conditions is longer than
|
||||
* a single element.
|
||||
*
|
||||
* @param list<string> $conditions
|
||||
*/
|
||||
public static function condition(array $conditions, ?bool $conjunction = null): CssMediaQuery
|
||||
{
|
||||
if (\count($conditions) > 1 && $conjunction === null) {
|
||||
throw new \InvalidArgumentException('If conditions is longer than one element, conjunction may not be null.');
|
||||
}
|
||||
|
||||
return new CssMediaQuery($conditions, $conjunction ?? true);
|
||||
}
|
||||
|
||||
public function getModifier(): ?string
|
||||
{
|
||||
return $this->modifier;
|
||||
}
|
||||
|
||||
public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function isConjunction(): bool
|
||||
{
|
||||
return $this->conjunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getConditions(): array
|
||||
{
|
||||
return $this->conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this media query matches all media types.
|
||||
*/
|
||||
public function matchesAllTypes(): bool
|
||||
{
|
||||
return $this->type === null || strtolower($this->type) === 'all';
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges this with $other to return a query that matches the intersection
|
||||
* of both inputs.
|
||||
*/
|
||||
public function merge(CssMediaQuery $other): MediaQueryMergeResult
|
||||
{
|
||||
if (!$this->conjunction || !$other->conjunction) {
|
||||
return MediaQuerySingletonMergeResult::unrepresentable;
|
||||
}
|
||||
|
||||
$ourModifier = $this->modifier !== null ? strtolower($this->modifier) : null;
|
||||
$ourType = $this->type !== null ? strtolower($this->type) : null;
|
||||
$theirModifier = $other->modifier !== null ? strtolower($other->modifier) : null;
|
||||
$theirType = $other->type !== null ? strtolower($other->type) : null;
|
||||
|
||||
if ($ourType === null && $theirType === null) {
|
||||
return self::condition(array_merge($this->conditions, $other->conditions), true);
|
||||
}
|
||||
|
||||
if (($ourModifier === 'not') !== ($theirModifier === 'not')) {
|
||||
if ($ourType === $theirType) {
|
||||
$negativeConditions = $ourModifier === 'not' ? $this->conditions : $other->conditions;
|
||||
$positiveConditions = $ourModifier === 'not' ? $other->conditions : $this->conditions;
|
||||
|
||||
// If the negative conditions are a subset of the positive conditions, the
|
||||
// query is empty. For example, `not screen and (color)` has no
|
||||
// intersection with `screen and (color) and (grid)`.
|
||||
//
|
||||
// However, `not screen and (color)` *does* intersect with `screen and
|
||||
// (grid)`, because it means `not (screen and (color))` and so it allows
|
||||
// a screen with no color but with a grid.
|
||||
if (empty(array_diff($negativeConditions, $positiveConditions))) {
|
||||
return MediaQuerySingletonMergeResult::empty;
|
||||
}
|
||||
|
||||
return MediaQuerySingletonMergeResult::unrepresentable;
|
||||
}
|
||||
|
||||
if ($this->matchesAllTypes() || $other->matchesAllTypes()) {
|
||||
return MediaQuerySingletonMergeResult::unrepresentable;
|
||||
}
|
||||
|
||||
if ($ourModifier === 'not') {
|
||||
$modifier = $theirModifier;
|
||||
$type = $theirType;
|
||||
$conditions = $other->conditions;
|
||||
} else {
|
||||
$modifier = $ourModifier;
|
||||
$type = $ourType;
|
||||
$conditions = $this->conditions;
|
||||
}
|
||||
} elseif ($ourModifier === 'not') {
|
||||
// CSS has no way of representing "neither screen nor print".
|
||||
if ($ourType !== $theirType) {
|
||||
return MediaQuerySingletonMergeResult::unrepresentable;
|
||||
}
|
||||
|
||||
$moreConditions = \count($this->conditions) > \count($other->conditions) ? $this->conditions : $other->conditions;
|
||||
$fewerConditions = \count($this->conditions) > \count($other->conditions) ? $other->conditions : $this->conditions;
|
||||
|
||||
// If one set of features is a superset of the other, use those features
|
||||
// because they're strictly narrower.
|
||||
if (empty(array_diff($fewerConditions, $moreConditions))) {
|
||||
$modifier = $ourModifier; // "not"
|
||||
$type = $ourType;
|
||||
$conditions = $moreConditions;
|
||||
} else {
|
||||
// Otherwise, there's no way to represent the intersection.
|
||||
return MediaQuerySingletonMergeResult::unrepresentable;
|
||||
}
|
||||
} elseif ($this->matchesAllTypes()) {
|
||||
$modifier = $theirModifier;
|
||||
// Omit the type if either input query did, since that indicates that they
|
||||
// aren't targeting a browser that requires "all and".
|
||||
$type = $other->matchesAllTypes() && $ourType === null ? null : $theirType;
|
||||
$conditions = array_merge($this->conditions, $other->conditions);
|
||||
} elseif ($other->matchesAllTypes()) {
|
||||
$modifier = $ourModifier;
|
||||
$type = $ourType;
|
||||
$conditions = array_merge($this->conditions, $other->conditions);
|
||||
} elseif ($ourType !== $theirType) {
|
||||
return MediaQuerySingletonMergeResult::empty;
|
||||
} else {
|
||||
$modifier = $ourModifier ?? $theirModifier;
|
||||
$type = $ourType;
|
||||
$conditions = array_merge($this->conditions, $other->conditions);
|
||||
}
|
||||
|
||||
return CssMediaQuery::type(
|
||||
$type === $ourType ? $this->type : $other->type,
|
||||
$modifier === $ourModifier ? $this->modifier : $other->modifier,
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
public function equals(object $other): bool
|
||||
{
|
||||
return $other instanceof CssMediaQuery && $other->modifier === $this->modifier && $other->type === $this->type && $other->conditions === $this->conditions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A plain CSS `@media` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssMediaRule extends CssParentNode
|
||||
{
|
||||
/**
|
||||
* The queries for this rule.
|
||||
*
|
||||
* This is never empty.
|
||||
*
|
||||
* @return list<CssMediaQuery>
|
||||
*/
|
||||
public function getQueries(): array;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\AstNode;
|
||||
use ScssPhp\ScssPhp\Visitor\CssVisitor;
|
||||
|
||||
/**
|
||||
* A statement in a plain CSS syntax tree.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssNode extends AstNode
|
||||
{
|
||||
/**
|
||||
* The node that contains this, or `null` for the root {@see CssStylesheet} node.
|
||||
*/
|
||||
public function getParent(): ?CssParentNode;
|
||||
|
||||
/**
|
||||
* Whether this was generated from the last node in a nested Sass tree that
|
||||
* got flattened during evaluation.
|
||||
*/
|
||||
public function isGroupEnd(): bool;
|
||||
|
||||
/**
|
||||
* Calls the appropriate visit method on $visitor.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @param CssVisitor<T> $visitor
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
public function accept(CssVisitor $visitor);
|
||||
|
||||
/**
|
||||
* Whether this is invisible and won't be emitted to the compiled stylesheet.
|
||||
*
|
||||
* Note that this doesn't consider nodes that contain loud comments to be
|
||||
* invisible even though they're omitted in compressed mode.
|
||||
*/
|
||||
public function isInvisible(): bool;
|
||||
|
||||
/**
|
||||
* Whether this node would be invisible even if style rule selectors within it
|
||||
* didn't have bogus combinators.
|
||||
*
|
||||
* Note that this doesn't consider nodes that contain loud comments to be
|
||||
* invisible even though they're omitted in compressed mode.
|
||||
*/
|
||||
public function isInvisibleOtherThanBogusCombinators(): bool;
|
||||
|
||||
/**
|
||||
* Whether this node will be invisible when loud comments are stripped.
|
||||
*/
|
||||
public function isInvisibleHidingComments(): bool;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A {@see CssNode} that can have child statements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssParentNode extends CssNode
|
||||
{
|
||||
/**
|
||||
* The child statements of this node.
|
||||
*
|
||||
* @return list<CssNode>
|
||||
*/
|
||||
public function getChildren(): array;
|
||||
|
||||
/**
|
||||
* Whether the rule has no children and should be emitted without curly
|
||||
* braces.
|
||||
*
|
||||
* This implies `children.isEmpty`, but the reverse is not true—for a rule
|
||||
* like `@foo {}`, {@see getChildren} is empty but {@see isChildless} is `false`.
|
||||
*/
|
||||
public function isChildless(): bool;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Selector\SelectorList;
|
||||
|
||||
/**
|
||||
* A plain CSS style rule.
|
||||
* *
|
||||
* * This applies style declarations to elements that match a given selector.
|
||||
* * Note that this isn't *strictly* plain CSS, since {@see getSelector} may still
|
||||
* * contain placeholder selectors.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssStyleRule extends CssParentNode
|
||||
{
|
||||
/**
|
||||
* The selector for this rule.
|
||||
*/
|
||||
public function getSelector(): SelectorList;
|
||||
|
||||
/**
|
||||
* The selector for this rule, before any extensions were applied.
|
||||
*/
|
||||
public function getOriginalSelector(): SelectorList;
|
||||
|
||||
/**
|
||||
* Whether this style rule was originally defined in a plain CSS stylesheet.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function isFromPlainCss(): bool;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A plain CSS stylesheet.
|
||||
*
|
||||
* This is the root plain CSS node. It contains top-level statements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssStylesheet extends CssParentNode
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A plain CSS `@supports` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface CssSupportsRule extends CssParentNode
|
||||
{
|
||||
/**
|
||||
* The supports condition.
|
||||
*
|
||||
* @return CssValue<string>
|
||||
*/
|
||||
public function getCondition(): CssValue;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\AstNode;
|
||||
use ScssPhp\ScssPhp\Ast\Selector\Combinator;
|
||||
use ScssPhp\ScssPhp\Util\Equatable;
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A value in a plain CSS tree.
|
||||
*
|
||||
* This is used to associate a span with a value that doesn't otherwise track
|
||||
* its span. It has value equality semantics.
|
||||
*
|
||||
* @template-covariant T of string|\Stringable|array<string|\Stringable>|Combinator|null
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CssValue implements AstNode, Equatable
|
||||
{
|
||||
/**
|
||||
* @var T
|
||||
*/
|
||||
private readonly mixed $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
*/
|
||||
public function __construct(mixed $value, FileSpan $span)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
public function getValue(): mixed
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function equals(object $other): bool
|
||||
{
|
||||
return $other instanceof CssValue && EquatableUtil::equals($this->value, $other->value);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->value instanceof Combinator) {
|
||||
return $this->value->getText();
|
||||
}
|
||||
|
||||
if (\is_array($this->value)) {
|
||||
return implode($this->value);
|
||||
}
|
||||
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\EveryCssVisitor;
|
||||
|
||||
/**
|
||||
* The visitor used to implement {@see CssNode::isInvisible}
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IsInvisibleVisitor extends EveryCssVisitor
|
||||
{
|
||||
/**
|
||||
* Whether to consider selectors with bogus combinators invisible.
|
||||
*/
|
||||
private readonly bool $includeBogus;
|
||||
|
||||
/**
|
||||
* Whether to consider comments invisible.
|
||||
*/
|
||||
private readonly bool $includeComments;
|
||||
|
||||
public function __construct(bool $includeBogus, bool $includeComments)
|
||||
{
|
||||
$this->includeBogus = $includeBogus;
|
||||
$this->includeComments = $includeComments;
|
||||
}
|
||||
|
||||
public function visitCssAtRule(CssAtRule $node): bool
|
||||
{
|
||||
// An unknown at-rule is never invisible. Because we don't know the semantics
|
||||
// of unknown rules, we can't guarantee that (for example) `@foo {}` isn't
|
||||
// meaningful.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitCssComment(CssComment $node): bool
|
||||
{
|
||||
return $this->includeComments && !$node->isPreserved();
|
||||
}
|
||||
|
||||
public function visitCssStyleRule(CssStyleRule $node): bool
|
||||
{
|
||||
return ($this->includeBogus ? $node->getSelector()->isInvisible() : $node->getSelector()->isInvisibleOtherThanBogusCombinators()) || parent::visitCssStyleRule($node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use JiriPudil\SealedClasses\Sealed;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[Sealed(permits: [CssMediaQuery::class, MediaQuerySingletonMergeResult::class])]
|
||||
interface MediaQueryMergeResult
|
||||
{
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
enum MediaQuerySingletonMergeResult implements MediaQueryMergeResult
|
||||
{
|
||||
case empty;
|
||||
case unrepresentable;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssAtRule} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssAtRule extends ModifiableCssParentNode implements CssAtRule
|
||||
{
|
||||
/**
|
||||
* @var CssValue<string>
|
||||
*/
|
||||
private readonly CssValue $name;
|
||||
|
||||
/**
|
||||
* @var CssValue<string>|null
|
||||
*/
|
||||
private readonly ?CssValue $value;
|
||||
|
||||
private readonly bool $childless;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param CssValue<string> $name
|
||||
* @param CssValue<string>|null $value
|
||||
*/
|
||||
public function __construct(CssValue $name, FileSpan $span, bool $childless = false, ?CssValue $value = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->childless = $childless;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getName(): CssValue
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getValue(): ?CssValue
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function isChildless(): bool
|
||||
{
|
||||
return $this->childless;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssAtRule($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssAtRule && EquatableUtil::equals($this->name, $other->name) && EquatableUtil::equals($this->value, $other->value) && $this->childless === $other->childless;
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssAtRule
|
||||
{
|
||||
return new ModifiableCssAtRule($this->name, $this->span, $this->childless, $this->value);
|
||||
}
|
||||
|
||||
public function addChild(ModifiableCssNode $child): void
|
||||
{
|
||||
if ($this->childless) {
|
||||
throw new \LogicException('Cannot add a child in a childless at-rule.');
|
||||
}
|
||||
|
||||
parent::addChild($child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssComment} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssComment extends ModifiableCssNode implements CssComment
|
||||
{
|
||||
private readonly string $text;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $text, FileSpan $span)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function isPreserved(): bool
|
||||
{
|
||||
return $this->text[2] === '!';
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssComment($this);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\StackTrace\Trace;
|
||||
use ScssPhp\ScssPhp\Value\SassString;
|
||||
use ScssPhp\ScssPhp\Value\Value;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssDeclaration} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssDeclaration extends ModifiableCssNode implements CssDeclaration
|
||||
{
|
||||
/**
|
||||
* @var CssValue<string>
|
||||
*/
|
||||
private readonly CssValue $name;
|
||||
|
||||
/**
|
||||
* @var CssValue<Value>
|
||||
*/
|
||||
private readonly CssValue $value;
|
||||
|
||||
/**
|
||||
* @var list<CssStyleRule>
|
||||
*/
|
||||
private readonly array $interleavedRules;
|
||||
|
||||
private readonly ?Trace $trace;
|
||||
|
||||
private readonly bool $parsedAsCustomProperty;
|
||||
|
||||
private readonly FileSpan $valueSpanForMap;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param CssValue<string> $name
|
||||
* @param CssValue<Value> $value
|
||||
* @param list<CssStyleRule> $interleavedRules
|
||||
*/
|
||||
public function __construct(CssValue $name, CssValue $value, FileSpan $span, bool $parsedAsCustomProperty, array $interleavedRules = [], ?Trace $trace = null, ?FileSpan $valueSpanForMap = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->parsedAsCustomProperty = $parsedAsCustomProperty;
|
||||
$this->interleavedRules = $interleavedRules;
|
||||
$this->trace = $trace;
|
||||
$this->valueSpanForMap = $valueSpanForMap ?? $value->getSpan();
|
||||
$this->span = $span;
|
||||
|
||||
if ($parsedAsCustomProperty) {
|
||||
if (!$this->isCustomProperty()) {
|
||||
throw new \InvalidArgumentException('parsedAsCustomProperty must be false if name doesn\'t begin with "--".');
|
||||
}
|
||||
|
||||
if (!$value->getValue() instanceof SassString) {
|
||||
throw new \InvalidArgumentException(sprintf('If parsedAsCustomProperty is true, value must contain a SassString (was %s).', get_debug_type($value->getValue())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): CssValue
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getValue(): CssValue
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getInterleavedRules(): array
|
||||
{
|
||||
return $this->interleavedRules;
|
||||
}
|
||||
|
||||
public function getTrace(): ?Trace
|
||||
{
|
||||
return $this->trace;
|
||||
}
|
||||
|
||||
public function isParsedAsCustomProperty(): bool
|
||||
{
|
||||
return $this->parsedAsCustomProperty;
|
||||
}
|
||||
|
||||
public function getValueSpanForMap(): FileSpan
|
||||
{
|
||||
return $this->valueSpanForMap;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function isCustomProperty(): bool
|
||||
{
|
||||
return str_starts_with($this->name->getValue(), '--');
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssDeclaration($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssImport} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssImport extends ModifiableCssNode implements CssImport
|
||||
{
|
||||
/**
|
||||
* The URL being imported.
|
||||
*
|
||||
* This includes quotes.
|
||||
*
|
||||
* @var CssValue<string>
|
||||
*/
|
||||
private readonly CssValue $url;
|
||||
|
||||
/**
|
||||
* @var CssValue<string>|null
|
||||
*/
|
||||
private readonly ?CssValue $modifiers;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param CssValue<string> $url
|
||||
* @param CssValue<string>|null $modifiers
|
||||
*/
|
||||
public function __construct(CssValue $url, FileSpan $span, ?CssValue $modifiers = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->modifiers = $modifiers;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getUrl(): CssValue
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getModifiers(): ?CssValue
|
||||
{
|
||||
return $this->modifiers;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssImport($this);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssKeyframeBlock} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssKeyframeBlock extends ModifiableCssParentNode implements CssKeyframeBlock
|
||||
{
|
||||
/**
|
||||
* @var CssValue<list<string>>
|
||||
*/
|
||||
private readonly CssValue $selector;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param CssValue<list<string>> $selector
|
||||
*/
|
||||
public function __construct(CssValue $selector, FileSpan $span)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->selector = $selector;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getSelector(): CssValue
|
||||
{
|
||||
return $this->selector;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssKeyframeBlock($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssKeyframeBlock && EquatableUtil::listEquals($this->selector->getValue(), $other->selector->getValue());
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssKeyframeBlock
|
||||
{
|
||||
return new ModifiableCssKeyframeBlock($this->selector, $this->span);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssMediaRule} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssMediaRule extends ModifiableCssParentNode implements CssMediaRule
|
||||
{
|
||||
/**
|
||||
* @var list<CssMediaQuery>
|
||||
*/
|
||||
private readonly array $queries;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<CssMediaQuery> $queries
|
||||
*/
|
||||
public function __construct(array $queries, FileSpan $span)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->queries = $queries;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getQueries(): array
|
||||
{
|
||||
return $this->queries;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssMediaRule($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssMediaRule && EquatableUtil::listEquals($this->queries, $other->queries);
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssMediaRule
|
||||
{
|
||||
return new ModifiableCssMediaRule($this->queries, $this->span);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Serializer\Serializer;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssNode}.
|
||||
*
|
||||
* Almost all CSS nodes are the modifiable classes under the covers. However,
|
||||
* modification should only be done within the evaluation step, so the
|
||||
* unmodifiable types are used elsewhere to enforce that constraint.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class ModifiableCssNode implements CssNode
|
||||
{
|
||||
private ?ModifiableCssParentNode $parent = null;
|
||||
|
||||
/**
|
||||
* The index of `$this` in parent's children.
|
||||
*
|
||||
* This makes {@see remove} more efficient.
|
||||
*/
|
||||
private ?int $indexInParent = null;
|
||||
|
||||
private bool $groupEnd = false;
|
||||
|
||||
public function getParent(): ?ModifiableCssParentNode
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
protected function setParent(ModifiableCssParentNode $parent, int $indexInParent): void
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->indexInParent = $indexInParent;
|
||||
}
|
||||
|
||||
public function isGroupEnd(): bool
|
||||
{
|
||||
return $this->groupEnd;
|
||||
}
|
||||
|
||||
public function setGroupEnd(bool $groupEnd): void
|
||||
{
|
||||
$this->groupEnd = $groupEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this node has a visible sibling after it.
|
||||
*/
|
||||
public function hasFollowingSibling(): bool
|
||||
{
|
||||
$parent = $this->parent;
|
||||
|
||||
if ($parent === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert($this->indexInParent !== null);
|
||||
$siblings = $parent->getChildren();
|
||||
|
||||
for ($i = $this->indexInParent + 1; $i < \count($siblings); $i++) {
|
||||
$sibling = $siblings[$i];
|
||||
|
||||
if (!$sibling->isInvisible()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isInvisible(): bool
|
||||
{
|
||||
return $this->accept(new IsInvisibleVisitor(true, false));
|
||||
}
|
||||
|
||||
public function isInvisibleOtherThanBogusCombinators(): bool
|
||||
{
|
||||
return $this->accept(new IsInvisibleVisitor(false, false));
|
||||
}
|
||||
|
||||
public function isInvisibleHidingComments(): bool
|
||||
{
|
||||
return $this->accept(new IsInvisibleVisitor(true, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the appropriate visit method on $visitor.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @param ModifiableCssVisitor<T> $visitor
|
||||
*
|
||||
* @return T
|
||||
*/
|
||||
abstract public function accept(ModifiableCssVisitor $visitor);
|
||||
|
||||
/**
|
||||
* Removes $this from {@see parent}'s child list.
|
||||
*
|
||||
* @throws \LogicException if {@see parent} is `null`.
|
||||
*/
|
||||
public function remove(): void
|
||||
{
|
||||
$parent = $this->parent;
|
||||
|
||||
if ($parent === null) {
|
||||
throw new \LogicException("Can't remove a node without a parent.");
|
||||
}
|
||||
|
||||
assert($this->indexInParent !== null);
|
||||
|
||||
$parent->removeChildAt($this->indexInParent);
|
||||
$children = $parent->getChildren();
|
||||
|
||||
for ($i = $this->indexInParent; $i < \count($children); $i++) {
|
||||
$child = $children[$i];
|
||||
assert($child->indexInParent !== null);
|
||||
$child->indexInParent = $child->indexInParent - 1;
|
||||
}
|
||||
$this->parent = null;
|
||||
$this->indexInParent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function resetParentReferences(): void
|
||||
{
|
||||
$this->parent = null;
|
||||
$this->indexInParent = null;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return Serializer::serialize($this, true)->css;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssParentNode} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class ModifiableCssParentNode extends ModifiableCssNode implements CssParentNode
|
||||
{
|
||||
/**
|
||||
* @var list<ModifiableCssNode>
|
||||
*/
|
||||
private array $children;
|
||||
|
||||
/**
|
||||
* @param list<ModifiableCssNode> $children
|
||||
*/
|
||||
public function __construct(array $children = [])
|
||||
{
|
||||
$this->children = $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<ModifiableCssNode>
|
||||
*/
|
||||
public function getChildren(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function isChildless(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether $this is equal to $other, ignoring their child nodes.
|
||||
*/
|
||||
abstract public function equalsIgnoringChildren(ModifiableCssNode $other): bool;
|
||||
|
||||
/**
|
||||
* Returns a copy of $this with an empty {@see children} list.
|
||||
*
|
||||
* This is *not* a deep copy. If other parts of this node are modifiable,
|
||||
* they are shared between the new and old nodes.
|
||||
*/
|
||||
abstract public function copyWithoutChildren(): ModifiableCssParentNode;
|
||||
|
||||
public function addChild(ModifiableCssNode $child): void
|
||||
{
|
||||
$child->setParent($this, \count($this->children));
|
||||
$this->children[] = $child;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function removeChildAt(int $index): void
|
||||
{
|
||||
array_splice($this->children, $index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructively removes all elements from {@see children}.
|
||||
*/
|
||||
public function clearChildren(): void
|
||||
{
|
||||
foreach ($this->children as $child) {
|
||||
$child->resetParentReferences();
|
||||
}
|
||||
$this->children = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Selector\SelectorList;
|
||||
use ScssPhp\ScssPhp\Util\Box;
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssStyleRule} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssStyleRule extends ModifiableCssParentNode implements CssStyleRule
|
||||
{
|
||||
/**
|
||||
* A reference to the modifiable selector list provided by the extension
|
||||
* store, which may update it over time as new extensions are applied.
|
||||
*
|
||||
* @var Box<SelectorList>
|
||||
*/
|
||||
private readonly Box $selector;
|
||||
|
||||
private readonly SelectorList $originalSelector;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private readonly bool $fromPlainCss;
|
||||
|
||||
/**
|
||||
* @param Box<SelectorList> $selector
|
||||
*/
|
||||
public function __construct(Box $selector, FileSpan $span, ?SelectorList $originalSelector = null, bool $fromPlainCss = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->selector = $selector;
|
||||
$this->originalSelector = $originalSelector ?? $selector->getValue();
|
||||
$this->span = $span;
|
||||
$this->fromPlainCss = $fromPlainCss;
|
||||
}
|
||||
|
||||
public function getSelector(): SelectorList
|
||||
{
|
||||
return $this->selector->getValue();
|
||||
}
|
||||
|
||||
public function getOriginalSelector(): SelectorList
|
||||
{
|
||||
return $this->originalSelector;
|
||||
}
|
||||
|
||||
public function isFromPlainCss(): bool
|
||||
{
|
||||
return $this->fromPlainCss;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssStyleRule($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssStyleRule && EquatableUtil::equals($this->selector, $other->selector);
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssStyleRule
|
||||
{
|
||||
return new ModifiableCssStyleRule($this->selector, $this->span, $this->originalSelector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssStylesheet} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssStylesheet extends ModifiableCssParentNode implements CssStylesheet
|
||||
{
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<ModifiableCssNode> $children
|
||||
*/
|
||||
public function __construct(FileSpan $span, array $children = [])
|
||||
{
|
||||
parent::__construct($children);
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssStylesheet($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssStylesheet;
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssStylesheet
|
||||
{
|
||||
return new ModifiableCssStylesheet($this->span);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Css;
|
||||
|
||||
use ScssPhp\ScssPhp\Util\EquatableUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A modifiable version of {@see CssSupportsRule} for use in the evaluation step.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ModifiableCssSupportsRule extends ModifiableCssParentNode implements CssSupportsRule
|
||||
{
|
||||
/**
|
||||
* @var CssValue<string>
|
||||
*/
|
||||
private readonly CssValue $condition;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param CssValue<string> $condition
|
||||
*/
|
||||
public function __construct(CssValue $condition, FileSpan $span)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->condition = $condition;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getCondition(): CssValue
|
||||
{
|
||||
return $this->condition;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ModifiableCssVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitCssSupportsRule($this);
|
||||
}
|
||||
|
||||
public function equalsIgnoringChildren(ModifiableCssNode $other): bool
|
||||
{
|
||||
return $other instanceof ModifiableCssSupportsRule && EquatableUtil::equals($this->condition, $other->condition);
|
||||
}
|
||||
|
||||
public function copyWithoutChildren(): ModifiableCssSupportsRule
|
||||
{
|
||||
return new ModifiableCssSupportsRule($this->condition, $this->span);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast;
|
||||
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An {@see AstNode} that just exposes a single span generated by a callback.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FakeAstNode implements AstNode
|
||||
{
|
||||
/**
|
||||
* @var \Closure(): FileSpan
|
||||
*/
|
||||
private readonly \Closure $callback;
|
||||
|
||||
/**
|
||||
* @param callable(): FileSpan $callback
|
||||
*/
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback(...);
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return ($this->callback)();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Util;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An argument declared as part of an {@see ArgumentDeclaration}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Argument implements SassNode, SassDeclaration
|
||||
{
|
||||
private readonly string $name;
|
||||
|
||||
private readonly ?Expression $defaultValue;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $name, FileSpan $span, ?Expression $defaultValue = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->defaultValue = $defaultValue;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The variable name as written in the document, without underscores
|
||||
* converted to hyphens and including the leading `$`.
|
||||
*
|
||||
* This isn't particularly efficient, and should only be used for error
|
||||
* messages.
|
||||
*/
|
||||
public function getOriginalName(): string
|
||||
{
|
||||
if ($this->defaultValue === null) {
|
||||
return $this->span->getText();
|
||||
}
|
||||
|
||||
return Util::declarationName($this->span);
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
if ($this->defaultValue === null) {
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($this->span, 1);
|
||||
}
|
||||
|
||||
public function getDefaultValue(): ?Expression
|
||||
{
|
||||
return $this->defaultValue;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
if ($this->defaultValue === null) {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
return $this->name . ': ' . $this->defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use League\Uri\Contracts\UriInterface;
|
||||
use ScssPhp\ScssPhp\Exception\MultiSpanSassScriptException;
|
||||
use ScssPhp\ScssPhp\Exception\SassFormatException;
|
||||
use ScssPhp\ScssPhp\Exception\SassScriptException;
|
||||
use ScssPhp\ScssPhp\Logger\LoggerInterface;
|
||||
use ScssPhp\ScssPhp\Parser\ScssParser;
|
||||
use ScssPhp\ScssPhp\Util\Character;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Util\StringUtil;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An argument declaration, as for a function or mixin definition.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ArgumentDeclaration implements SassNode
|
||||
{
|
||||
/**
|
||||
* @var list<Argument>
|
||||
*/
|
||||
private readonly array $arguments;
|
||||
|
||||
private readonly ?string $restArgument;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<Argument> $arguments
|
||||
*/
|
||||
public function __construct(array $arguments, FileSpan $span, ?string $restArgument = null)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
$this->restArgument = $restArgument;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public static function createEmpty(FileSpan $span): ArgumentDeclaration
|
||||
{
|
||||
return new self([], $span);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an argument declaration from $contents, which should be of the
|
||||
* form `@rule name(args) {`.
|
||||
*
|
||||
* If passed, $url is the name of the file from which $contents comes.
|
||||
*
|
||||
* @throws SassFormatException if parsing fails.
|
||||
*/
|
||||
public static function parse(string $contents, ?LoggerInterface $logger = null, ?UriInterface $url = null): ArgumentDeclaration
|
||||
{
|
||||
return (new ScssParser($contents, $logger, $url))->parseArgumentDeclaration();
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return \count($this->arguments) === 0 && $this->restArgument === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Argument>
|
||||
*/
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getRestArgument(): ?string
|
||||
{
|
||||
return $this->restArgument;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@see $span} expanded to include an identifier immediately before the
|
||||
* declaration, if possible.
|
||||
*/
|
||||
public function getSpanWithName(): FileSpan
|
||||
{
|
||||
$text = $this->span->getFile()->getText(0);
|
||||
|
||||
// Move backwards through any whitespace between the name and the arguments.
|
||||
$i = $this->span->getStart()->getOffset() - 1;
|
||||
while ($i > 0 && Character::isWhitespace($text[$i])) {
|
||||
$i--;
|
||||
}
|
||||
|
||||
// Then move backwards through the name itself.
|
||||
if (!Character::isName($text[$i])) {
|
||||
return $this->span;
|
||||
}
|
||||
$i--;
|
||||
while ($i >= 0 && Character::isName($text[$i])) {
|
||||
$i--;
|
||||
}
|
||||
|
||||
// Trim because it's possible that this span is empty (for example, a mixin
|
||||
// may be declared without an argument list).
|
||||
return SpanUtil::trim($this->span->getFile()->span($i + 1, $this->span->getEnd()->getOffset()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $names Only keys are relevant
|
||||
*
|
||||
* @throws SassScriptException if $positional and $names aren't valid for this argument declaration.
|
||||
*/
|
||||
public function verify(int $positional, array $names): void
|
||||
{
|
||||
$nameUsed = 0;
|
||||
|
||||
foreach ($this->arguments as $i => $argument) {
|
||||
if ($i < $positional) {
|
||||
if (isset($names[$argument->getName()])) {
|
||||
$originalName = $this->originalArgumentName($argument->getName());
|
||||
throw new SassScriptException(sprintf('Argument %s was passed both by position and by name.', $originalName));
|
||||
}
|
||||
} elseif (isset($names[$argument->getName()])) {
|
||||
$nameUsed++;
|
||||
} elseif ($argument->getDefaultValue() === null) {
|
||||
$originalName = $this->originalArgumentName($argument->getName());
|
||||
throw new MultiSpanSassScriptException(sprintf('Missing argument %s.', $originalName), 'invocation', ['declaration' => $this->getSpanWithName()]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->restArgument !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($positional > \count($this->arguments)) {
|
||||
$message = sprintf(
|
||||
'Only %d %s%s allowed, but %d %s passed.',
|
||||
\count($this->arguments),
|
||||
empty($names) ? '' : 'positional ',
|
||||
StringUtil::pluralize('argument', \count($this->arguments)),
|
||||
$positional,
|
||||
StringUtil::pluralize('was', $positional, 'were')
|
||||
);
|
||||
throw new MultiSpanSassScriptException($message, 'invocation', ['declaration' => $this->getSpanWithName()]);
|
||||
}
|
||||
|
||||
if ($nameUsed < \count($names)) {
|
||||
$unknownNames = array_values(array_diff(array_keys($names), array_map(fn($argument) => $argument->getName(), $this->arguments)));
|
||||
\assert(\count($unknownNames) > 0);
|
||||
$message = sprintf(
|
||||
'No %s named %s.',
|
||||
StringUtil::pluralize('argument', \count($unknownNames)),
|
||||
StringUtil::toSentence(array_map(fn ($name) => '$' . $name, $unknownNames), 'or')
|
||||
);
|
||||
throw new MultiSpanSassScriptException($message, 'invocation', ['declaration' => $this->getSpanWithName()]);
|
||||
}
|
||||
}
|
||||
|
||||
private function originalArgumentName(string $name): string
|
||||
{
|
||||
if ($name === $this->restArgument) {
|
||||
$text = $this->span->getText();
|
||||
$lastDollar = strrpos($text, '$');
|
||||
assert($lastDollar !== false);
|
||||
$fromDollar = substr($text, $lastDollar);
|
||||
$dot = strrpos($fromDollar, '.');
|
||||
assert($dot !== false);
|
||||
|
||||
return substr($fromDollar, 0, $dot);
|
||||
}
|
||||
|
||||
foreach ($this->arguments as $argument) {
|
||||
if ($argument->getName() === $name) {
|
||||
return $argument->getOriginalName();
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("This declaration has no argument named \"\$$name\".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether $positional and $names are valid for this argument
|
||||
* declaration.
|
||||
*
|
||||
* @param array<string, mixed> $names Only keys are relevant
|
||||
*/
|
||||
public function matches(int $positional, array $names): bool
|
||||
{
|
||||
$nameUsed = 0;
|
||||
|
||||
foreach ($this->arguments as $i => $argument) {
|
||||
if ($i < $positional) {
|
||||
if (isset($names[$argument->getName()])) {
|
||||
return false;
|
||||
}
|
||||
} elseif (isset($names[$argument->getName()])) {
|
||||
$nameUsed++;
|
||||
} elseif ($argument->getDefaultValue() === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->restArgument !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($positional > \count($this->arguments)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($nameUsed < \count($names)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->arguments as $arg) {
|
||||
$parts[] = "\$$arg";
|
||||
}
|
||||
if ($this->restArgument !== null) {
|
||||
$parts[] = "\$$this->restArgument...";
|
||||
}
|
||||
|
||||
return implode(', ', $parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression\ListExpression;
|
||||
use ScssPhp\ScssPhp\Value\ListSeparator;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A set of arguments passed in to a function or mixin.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ArgumentInvocation implements SassNode
|
||||
{
|
||||
/**
|
||||
* @var list<Expression>
|
||||
*/
|
||||
private readonly array $positional;
|
||||
|
||||
/**
|
||||
* @var array<string, Expression>
|
||||
*/
|
||||
private readonly array $named;
|
||||
|
||||
private readonly ?Expression $rest;
|
||||
|
||||
private readonly ?Expression $keywordRest;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<Expression> $positional
|
||||
* @param array<string, Expression> $named
|
||||
*/
|
||||
public function __construct(array $positional, array $named, FileSpan $span, ?Expression $rest = null, ?Expression $keywordRest = null)
|
||||
{
|
||||
assert($keywordRest === null || $rest !== null);
|
||||
|
||||
$this->positional = $positional;
|
||||
$this->named = $named;
|
||||
$this->rest = $rest;
|
||||
$this->keywordRest = $keywordRest;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public static function createEmpty(FileSpan $span): ArgumentInvocation
|
||||
{
|
||||
return new self([], [], $span);
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return \count($this->positional) === 0 && \count($this->named) === 0 && $this->rest === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Expression>
|
||||
*/
|
||||
public function getPositional(): array
|
||||
{
|
||||
return $this->positional;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Expression>
|
||||
*/
|
||||
public function getNamed(): array
|
||||
{
|
||||
return $this->named;
|
||||
}
|
||||
|
||||
public function getRest(): ?Expression
|
||||
{
|
||||
return $this->rest;
|
||||
}
|
||||
|
||||
public function getKeywordRest(): ?Expression
|
||||
{
|
||||
return $this->keywordRest;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($this->positional as $argument) {
|
||||
$parts[] = $this->parenthesizeArgument($argument);
|
||||
}
|
||||
foreach ($this->named as $name => $arg) {
|
||||
$parts[] = "\$$name: {$this->parenthesizeArgument($arg)}";
|
||||
}
|
||||
if ($this->rest !== null) {
|
||||
$parts[] = "{$this->parenthesizeArgument($this->rest)}...";
|
||||
}
|
||||
if ($this->keywordRest !== null) {
|
||||
$parts[] = "{$this->parenthesizeArgument($this->keywordRest)}...";
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $parts) . ')';
|
||||
}
|
||||
|
||||
private function parenthesizeArgument(Expression $argument): string
|
||||
{
|
||||
if ($argument instanceof ListExpression && $argument->getSeparator() === ListSeparator::COMMA && !$argument->hasBrackets() && \count($argument->getContents()) > 1) {
|
||||
return "($argument)";
|
||||
}
|
||||
|
||||
return (string) $argument;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use League\Uri\Contracts\UriInterface;
|
||||
use ScssPhp\ScssPhp\Ast\Css\CssAtRule;
|
||||
use ScssPhp\ScssPhp\Ast\Css\CssMediaRule;
|
||||
use ScssPhp\ScssPhp\Ast\Css\CssParentNode;
|
||||
use ScssPhp\ScssPhp\Ast\Css\CssStyleRule;
|
||||
use ScssPhp\ScssPhp\Ast\Css\CssSupportsRule;
|
||||
use ScssPhp\ScssPhp\Exception\SassFormatException;
|
||||
use ScssPhp\ScssPhp\Logger\LoggerInterface;
|
||||
use ScssPhp\ScssPhp\Parser\AtRootQueryParser;
|
||||
use ScssPhp\ScssPhp\Parser\InterpolationMap;
|
||||
|
||||
/**
|
||||
* A query for the `@at-root` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AtRootQuery
|
||||
{
|
||||
/**
|
||||
* Whether the query includes or excludes rules with the specified names.
|
||||
*/
|
||||
private readonly bool $include;
|
||||
|
||||
/**
|
||||
* The names of the rules included or excluded by this query.
|
||||
*
|
||||
* There are two special names. "all" indicates that all rules are included
|
||||
* or excluded, and "rule" indicates style rules are included or excluded.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private readonly array $names;
|
||||
|
||||
/**
|
||||
* Whether this includes or excludes *all* rules.
|
||||
*/
|
||||
private readonly bool $all;
|
||||
|
||||
/**
|
||||
* Whether this includes or excludes style rules.
|
||||
*/
|
||||
private readonly bool $rule;
|
||||
|
||||
/**
|
||||
* Parses an at-root query from $contents.
|
||||
*
|
||||
* If passed, $url is the name of the file from which $contents comes.
|
||||
*
|
||||
* @throws SassFormatException if parsing fails
|
||||
*/
|
||||
public static function parse(string $contents, ?LoggerInterface $logger = null, ?UriInterface $url = null, ?InterpolationMap $interpolationMap = null): AtRootQuery
|
||||
{
|
||||
return (new AtRootQueryParser($contents, $logger, $url, $interpolationMap))->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $names
|
||||
*/
|
||||
public static function create(array $names, bool $include): AtRootQuery
|
||||
{
|
||||
return new AtRootQuery($names, $include, \in_array('all', $names, true), \in_array('rule', $names, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* The default at-root query
|
||||
*/
|
||||
public static function getDefault(): AtRootQuery
|
||||
{
|
||||
return new AtRootQuery([], false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $names
|
||||
*/
|
||||
private function __construct(array $names, bool $include, bool $all, bool $rule)
|
||||
{
|
||||
$this->include = $include;
|
||||
$this->names = $names;
|
||||
$this->all = $all;
|
||||
$this->rule = $rule;
|
||||
}
|
||||
|
||||
public function getInclude(): bool
|
||||
{
|
||||
return $this->include;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
return $this->names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this excludes style rules.
|
||||
*
|
||||
* Note that this takes {@see include} into account.
|
||||
*/
|
||||
public function excludesStyleRules(): bool
|
||||
{
|
||||
return ($this->all || $this->rule) !== $this->include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether $this excludes $node
|
||||
*/
|
||||
public function excludes(CssParentNode $node): bool
|
||||
{
|
||||
if ($this->all) {
|
||||
return !$this->include;
|
||||
}
|
||||
|
||||
if ($node instanceof CssStyleRule) {
|
||||
return $this->excludesStyleRules();
|
||||
}
|
||||
|
||||
if ($node instanceof CssMediaRule) {
|
||||
return $this->excludesName('media');
|
||||
}
|
||||
|
||||
if ($node instanceof CssSupportsRule) {
|
||||
return $this->excludesName('supports');
|
||||
}
|
||||
|
||||
if ($node instanceof CssAtRule) {
|
||||
return $this->excludesName(strtolower($node->getName()->getValue()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether $this excludes an at-rule with the given $name.
|
||||
*/
|
||||
public function excludesName(string $name): bool
|
||||
{
|
||||
return ($this->all || \in_array($name, $this->names, true)) !== $this->include;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface CallableInvocation extends SassNode
|
||||
{
|
||||
public function getArguments(): ArgumentInvocation;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A variable configured by a `with` clause in a `@use` or `@forward` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ConfiguredVariable implements SassNode, SassDeclaration
|
||||
{
|
||||
private readonly string $name;
|
||||
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private readonly bool $guarded;
|
||||
|
||||
public function __construct(string $name, Expression $expression, FileSpan $span, bool $guarded = false)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
$this->guarded = $guarded;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function isGuarded(): bool
|
||||
{
|
||||
return $this->guarded;
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
return SpanUtil::initialIdentifier($this->span, 1);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '$' . $this->name . ': ' . $this->expression . ($this->guarded ? ' !default' : '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
|
||||
/**
|
||||
* A SassScript expression in a Sass syntax tree.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface Expression extends SassNode
|
||||
{
|
||||
/**
|
||||
* @template T
|
||||
* @param ExpressionVisitor<T> $visitor
|
||||
* @return T
|
||||
*/
|
||||
public function accept(ExpressionVisitor $visitor);
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A binary operator, as in `1 + 2` or `$this and $other`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BinaryOperationExpression implements Expression
|
||||
{
|
||||
private readonly BinaryOperator $operator;
|
||||
|
||||
private readonly Expression $left;
|
||||
|
||||
private readonly Expression $right;
|
||||
|
||||
/**
|
||||
* Whether this is a dividedBy operation that may be interpreted as slash-separated numbers.
|
||||
*/
|
||||
private bool $allowsSlash = false;
|
||||
|
||||
public function __construct(BinaryOperator $operator, Expression $left, Expression $right)
|
||||
{
|
||||
$this->operator = $operator;
|
||||
$this->left = $left;
|
||||
$this->right = $right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dividedBy operation that may be interpreted as slash-separated numbers.
|
||||
*/
|
||||
public static function slash(Expression $left, Expression $right): self
|
||||
{
|
||||
$operation = new self(BinaryOperator::DIVIDED_BY, $left, $right);
|
||||
$operation->allowsSlash = true;
|
||||
|
||||
return $operation;
|
||||
}
|
||||
|
||||
public function getOperator(): BinaryOperator
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
public function getLeft(): Expression
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
public function getRight(): Expression
|
||||
{
|
||||
return $this->right;
|
||||
}
|
||||
|
||||
public function allowsSlash(): bool
|
||||
{
|
||||
return $this->allowsSlash;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
$left = $this->left;
|
||||
|
||||
while ($left instanceof BinaryOperationExpression) {
|
||||
$left = $left->left;
|
||||
}
|
||||
|
||||
$right = $this->right;
|
||||
|
||||
while ($right instanceof BinaryOperationExpression) {
|
||||
$right = $right->right;
|
||||
}
|
||||
|
||||
$leftSpan = $left->getSpan();
|
||||
$rightSpan = $right->getSpan();
|
||||
|
||||
return $leftSpan->expand($rightSpan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the span that covers only {@see $operator}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function getOperatorSpan(): FileSpan
|
||||
{
|
||||
$leftSpan = $this->left->getSpan();
|
||||
$rightSpan = $this->right->getSpan();
|
||||
|
||||
if ($leftSpan->getFile() === $rightSpan->getFile() && $leftSpan->getEnd()->getOffset() < $rightSpan->getStart()->getOffset()) {
|
||||
return SpanUtil::trim($leftSpan->getFile()->span($leftSpan->getEnd()->getOffset(), $rightSpan->getStart()->getOffset()));
|
||||
}
|
||||
|
||||
return $this->getSpan();
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitBinaryOperationExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
$leftNeedsParens = ($this->left instanceof BinaryOperationExpression && $this->left->getOperator()->getPrecedence() < $this->operator->getPrecedence()) || ($this->left instanceof ListExpression && !$this->left->hasBrackets() && \count($this->left->getContents()) > 1);
|
||||
if ($leftNeedsParens) {
|
||||
$buffer .= '(';
|
||||
}
|
||||
$buffer .= $this->left;
|
||||
if ($leftNeedsParens) {
|
||||
$buffer .= ')';
|
||||
}
|
||||
|
||||
$buffer .= ' ';
|
||||
$buffer .= $this->operator->getOperator();
|
||||
$buffer .= ' ';
|
||||
|
||||
$rightNeedsParens = ($this->right instanceof BinaryOperationExpression && $this->right->getOperator()->getPrecedence() <= $this->operator->getPrecedence() && !($this->right->operator === $this->operator && $this->operator->isAssociative())) || ($this->right instanceof ListExpression && !$this->right->hasBrackets() && \count($this->right->getContents()) > 1);
|
||||
if ($rightNeedsParens) {
|
||||
$buffer .= '(';
|
||||
}
|
||||
$buffer .= $this->right;
|
||||
if ($rightNeedsParens) {
|
||||
$buffer .= ')';
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
enum BinaryOperator
|
||||
{
|
||||
case SINGLE_EQUALS;
|
||||
case OR;
|
||||
case AND;
|
||||
case EQUALS;
|
||||
case NOT_EQUALS;
|
||||
case GREATER_THAN;
|
||||
case GREATER_THAN_OR_EQUALS;
|
||||
case LESS_THAN;
|
||||
case LESS_THAN_OR_EQUALS;
|
||||
case PLUS;
|
||||
case MINUS;
|
||||
case TIMES;
|
||||
case DIVIDED_BY;
|
||||
case MODULO;
|
||||
|
||||
/**
|
||||
* The Sass syntax for this operator
|
||||
*/
|
||||
public function getOperator(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::SINGLE_EQUALS => '=',
|
||||
self::OR => 'or',
|
||||
self::AND => 'and',
|
||||
self::EQUALS => '==',
|
||||
self::NOT_EQUALS => '!=',
|
||||
self::GREATER_THAN => '>',
|
||||
self::GREATER_THAN_OR_EQUALS => '>=',
|
||||
self::LESS_THAN => '<',
|
||||
self::LESS_THAN_OR_EQUALS => '<=',
|
||||
self::PLUS => '+',
|
||||
self::MINUS => '-',
|
||||
self::TIMES => '*',
|
||||
self::DIVIDED_BY => '/',
|
||||
self::MODULO => '%',
|
||||
};
|
||||
}
|
||||
|
||||
public function getPrecedence(): int
|
||||
{
|
||||
return match ($this) {
|
||||
self::SINGLE_EQUALS => 0,
|
||||
self::OR => 1,
|
||||
self::AND => 2,
|
||||
self::EQUALS, self::NOT_EQUALS => 3,
|
||||
self::GREATER_THAN, self::GREATER_THAN_OR_EQUALS, self::LESS_THAN, self::LESS_THAN_OR_EQUALS => 4,
|
||||
self::PLUS, self::MINUS => 5,
|
||||
self::TIMES, self::DIVIDED_BY, self::MODULO => 6,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this operation has the [associative property].
|
||||
*
|
||||
* [associative property]: https://en.wikipedia.org/wiki/Associative_property
|
||||
*/
|
||||
public function isAssociative(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::OR, self::AND, self::PLUS, self::TIMES => true,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A boolean literal, `true` or `false`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BooleanExpression implements Expression
|
||||
{
|
||||
private readonly bool $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(bool $value, FileSpan $span)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getValue(): bool
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitBooleanExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Value\SassColor;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A color literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ColorExpression implements Expression
|
||||
{
|
||||
private readonly SassColor $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(SassColor $value, FileSpan $span)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getValue(): SassColor
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitColorExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\CallableInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassReference;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A function invocation.
|
||||
*
|
||||
* This may be a plain CSS function or a Sass function, but may not include
|
||||
* interpolation.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FunctionExpression implements Expression, CallableInvocation, SassReference
|
||||
{
|
||||
/**
|
||||
* The name of the function being invoked, with underscores converted to
|
||||
* hyphens.
|
||||
*
|
||||
* If this function is a plain CSS function, use {@see $originalName} instead.
|
||||
*/
|
||||
private readonly string $name;
|
||||
|
||||
/**
|
||||
* The name of the function being invoked, with underscores left as-is.
|
||||
*/
|
||||
private readonly string $originalName;
|
||||
|
||||
/**
|
||||
* The arguments to pass to the function.
|
||||
*/
|
||||
private readonly ArgumentInvocation $arguments;
|
||||
|
||||
/**
|
||||
* The namespace of the function being invoked, or `null` if it's invoked
|
||||
* without a namespace.
|
||||
*/
|
||||
private readonly ?string $namespace;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $originalName, ArgumentInvocation $arguments, FileSpan $span, ?string $namespace = null)
|
||||
{
|
||||
$this->span = $span;
|
||||
$this->originalName = $originalName;
|
||||
$this->arguments = $arguments;
|
||||
$this->namespace = $namespace;
|
||||
$this->name = str_replace('_', '-', $this->originalName);
|
||||
}
|
||||
|
||||
public function getOriginalName(): string
|
||||
{
|
||||
return $this->originalName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the function being invoked, with underscores converted to
|
||||
* hyphens.
|
||||
*
|
||||
* If this function is a plain CSS function, use {@see getOriginalName} instead.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getArguments(): ArgumentInvocation
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getNamespace(): ?string
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return SpanUtil::initialIdentifier($this->span);
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier(SpanUtil::withoutNamespace($this->span));
|
||||
}
|
||||
|
||||
public function getNamespaceSpan(): ?FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($this->span);
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitFunctionExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '';
|
||||
|
||||
if ($this->namespace !== null) {
|
||||
$buffer .= $this->namespace . '.';
|
||||
}
|
||||
|
||||
$buffer .= $this->originalName . $this->arguments;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\CallableInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A ternary expression.
|
||||
*
|
||||
* This is defined as a separate syntactic construct rather than a normal
|
||||
* function because only one of the `$if-true` and `$if-false` arguments are
|
||||
* evaluated.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IfExpression implements Expression, CallableInvocation
|
||||
{
|
||||
/**
|
||||
* The arguments passed to `if()`.
|
||||
*/
|
||||
private readonly ArgumentInvocation $arguments;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private static ?ArgumentDeclaration $declaration = null;
|
||||
|
||||
public function __construct(ArgumentInvocation $arguments, FileSpan $span)
|
||||
{
|
||||
$this->span = $span;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* The declaration of `if()`, as though it were a normal function.
|
||||
*/
|
||||
public static function getDeclaration(): ArgumentDeclaration
|
||||
{
|
||||
if (self::$declaration === null) {
|
||||
self::$declaration = ArgumentDeclaration::parse('@function if($condition, $if-true, $if-false) {');
|
||||
}
|
||||
|
||||
return self::$declaration;
|
||||
}
|
||||
|
||||
public function getArguments(): ArgumentInvocation
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitIfExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'if' . $this->arguments;
|
||||
}
|
||||
}
|
||||
Vendored
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\CallableInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An interpolated function invocation.
|
||||
*
|
||||
* This is always a plain CSS function.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class InterpolatedFunctionExpression implements Expression, CallableInvocation
|
||||
{
|
||||
/**
|
||||
* The name of the function being invoked.
|
||||
*/
|
||||
private readonly Interpolation $name;
|
||||
|
||||
/**
|
||||
* The arguments to pass to the function.
|
||||
*/
|
||||
private readonly ArgumentInvocation $arguments;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Interpolation $name, ArgumentInvocation $arguments, FileSpan $span)
|
||||
{
|
||||
$this->span = $span;
|
||||
$this->name = $name;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function getName(): Interpolation
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getArguments(): ArgumentInvocation
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitInterpolatedFunctionExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name . $this->arguments;
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Util\IterableUtil;
|
||||
use ScssPhp\ScssPhp\Value\ListSeparator;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
|
||||
/**
|
||||
* @template-implements ExpressionVisitor<bool>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IsCalculationSafeVisitor implements ExpressionVisitor
|
||||
{
|
||||
public function visitBinaryOperationExpression(BinaryOperationExpression $node): bool
|
||||
{
|
||||
return \in_array($node->getOperator(), [BinaryOperator::TIMES, BinaryOperator::DIVIDED_BY, BinaryOperator::PLUS, BinaryOperator::MINUS], true) && ($node->getLeft()->accept($this) || $node->getRight()->accept($this));
|
||||
}
|
||||
|
||||
public function visitBooleanExpression(BooleanExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitColorExpression(ColorExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitFunctionExpression(FunctionExpression $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function visitInterpolatedFunctionExpression(InterpolatedFunctionExpression $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function visitIfExpression(IfExpression $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function visitListExpression(ListExpression $node): bool
|
||||
{
|
||||
return $node->getSeparator() === ListSeparator::SPACE && !$node->hasBrackets() && \count($node->getContents()) > 1 && IterableUtil::every($node->getContents(), fn(Expression $expression) => $expression->accept($this));
|
||||
}
|
||||
|
||||
public function visitMapExpression(MapExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitNullExpression(NullExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitNumberExpression(NumberExpression $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function visitParenthesizedExpression(ParenthesizedExpression $node): bool
|
||||
{
|
||||
return $node->getExpression()->accept($this);
|
||||
}
|
||||
|
||||
public function visitSelectorExpression(SelectorExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitStringExpression(StringExpression $node): bool
|
||||
{
|
||||
if ($node->hasQuotes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude non-identifier constructs that are parsed as {@see StringExpression}s.
|
||||
* We could just check if they parse as valid identifiers, but this is
|
||||
* cheaper.
|
||||
*/
|
||||
$text = $node->getText()->getInitialPlain();
|
||||
|
||||
// !important
|
||||
return !str_starts_with($text, '!')
|
||||
// ID-style identifiers
|
||||
&& !str_starts_with($text, '#')
|
||||
// Unicode ranges
|
||||
&& ($text[1] ?? null) !== '+'
|
||||
// url()
|
||||
&& ($text[3] ?? null) !== '(';
|
||||
}
|
||||
|
||||
public function visitSupportsExpression(SupportsExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitUnaryOperationExpression(UnaryOperationExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitValueExpression(ValueExpression $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function visitVariableExpression(VariableExpression $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Value\ListSeparator;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A list literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ListExpression implements Expression
|
||||
{
|
||||
/**
|
||||
* @var list<Expression>
|
||||
*/
|
||||
private readonly array $contents;
|
||||
|
||||
private readonly ListSeparator $separator;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private readonly bool $brackets;
|
||||
|
||||
/**
|
||||
* ListExpression constructor.
|
||||
*
|
||||
* @param list<Expression> $contents
|
||||
*/
|
||||
public function __construct(array $contents, ListSeparator $separator, FileSpan $span, bool $brackets = false)
|
||||
{
|
||||
$this->contents = $contents;
|
||||
$this->separator = $separator;
|
||||
$this->span = $span;
|
||||
$this->brackets = $brackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Expression>
|
||||
*/
|
||||
public function getContents(): array
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
public function getSeparator(): ListSeparator
|
||||
{
|
||||
return $this->separator;
|
||||
}
|
||||
|
||||
public function hasBrackets(): bool
|
||||
{
|
||||
return $this->brackets;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitListExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '';
|
||||
if ($this->hasBrackets()) {
|
||||
$buffer .= '[';
|
||||
} elseif (\count($this->contents) === 0 || (\count($this->contents) === 1 && $this->separator === ListSeparator::COMMA)) {
|
||||
$buffer .= '(';
|
||||
}
|
||||
|
||||
$buffer .= implode(
|
||||
$this->separator === ListSeparator::COMMA ? ', ' : ' ',
|
||||
array_map(fn($element) => $this->elementNeedsParens($element) ? "($element)" : (string) $element, $this->contents)
|
||||
);
|
||||
|
||||
if ($this->hasBrackets()) {
|
||||
$buffer .= ']';
|
||||
} elseif (\count($this->contents) === 0) {
|
||||
$buffer .= ')';
|
||||
} elseif (\count($this->contents) === 1 && $this->separator === ListSeparator::COMMA) {
|
||||
$buffer .= ',)';
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether $expression, contained in $this, needs parentheses when
|
||||
* printed as Sass source.
|
||||
*/
|
||||
private function elementNeedsParens(Expression $expression): bool
|
||||
{
|
||||
if ($expression instanceof ListExpression) {
|
||||
if (\count($expression->contents) < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($expression->brackets) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->separator === ListSeparator::COMMA ? $expression->separator === ListSeparator::COMMA : $expression->separator !== ListSeparator::UNDECIDED;
|
||||
}
|
||||
|
||||
if ($this->separator !== ListSeparator::SPACE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($expression instanceof UnaryOperationExpression) {
|
||||
return $expression->getOperator() === UnaryOperator::PLUS || $expression->getOperator() === UnaryOperator::MINUS;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A map literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MapExpression implements Expression
|
||||
{
|
||||
/**
|
||||
* @var list<array{Expression, Expression}>
|
||||
*/
|
||||
private readonly array $pairs;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<array{Expression, Expression}> $pairs
|
||||
*/
|
||||
public function __construct(array $pairs, FileSpan $span)
|
||||
{
|
||||
$this->pairs = $pairs;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{Expression, Expression}>
|
||||
*/
|
||||
public function getPairs(): array
|
||||
{
|
||||
return $this->pairs;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitMapExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '(' . implode(', ', array_map(fn($pair) => $pair[0] . ': ' . $pair[1], $this->pairs)) . ')';
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A null literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NullExpression implements Expression
|
||||
{
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(FileSpan $span)
|
||||
{
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitNullExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return 'null';
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Value\SassNumber;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A number literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NumberExpression implements Expression
|
||||
{
|
||||
private readonly float $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private readonly ?string $unit;
|
||||
|
||||
public function __construct(float $value, FileSpan $span, ?string $unit = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
$this->unit = $unit;
|
||||
}
|
||||
|
||||
public function getValue(): float
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function getUnit(): ?string
|
||||
{
|
||||
return $this->unit;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitNumberExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) SassNumber::create($this->value, $this->unit);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An expression wrapped in parentheses.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ParenthesizedExpression implements Expression
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Expression $expression, FileSpan $span)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitParenthesizedExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '(' . $this->expression . ')';
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A parent selector reference, `&`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SelectorExpression implements Expression
|
||||
{
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(FileSpan $span)
|
||||
{
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitSelectorExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '&';
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Parser\InterpolationBuffer;
|
||||
use ScssPhp\ScssPhp\Util\Character;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A string literal.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class StringExpression implements Expression
|
||||
{
|
||||
private readonly Interpolation $text;
|
||||
|
||||
private readonly bool $quotes;
|
||||
|
||||
public function __construct(Interpolation $text, bool $quotes = false)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->quotes = $quotes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string expression with no interpolation.
|
||||
*/
|
||||
public static function plain(string $text, FileSpan $span, bool $quotes = false): self
|
||||
{
|
||||
return new self(new Interpolation([$text], $span), $quotes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Sass source for a quoted string that, when evaluated, will have
|
||||
* $text as its contents.
|
||||
*/
|
||||
public static function quoteText(string $text): string
|
||||
{
|
||||
$quote = self::bestQuote([$text]);
|
||||
$buffer = $quote;
|
||||
$buffer .= self::quoteInnerText($text, $quote, true);
|
||||
$buffer .= $quote;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolation that, when evaluated, produces the contents of this string.
|
||||
*
|
||||
* Unlike {@see asInterpolation}, escapes are resolved and quotes are not
|
||||
* included.
|
||||
* If this is a quoted string, escapes are resolved and quotes are not
|
||||
* included in this text (unlike {@see asInterpolation}). If it's an unquoted
|
||||
* string, escapes are *not* resolved.
|
||||
*/
|
||||
public function getText(): Interpolation
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function hasQuotes(): bool
|
||||
{
|
||||
return $this->quotes;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->text->getSpan();
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitStringExpression($this);
|
||||
}
|
||||
|
||||
public function asInterpolation(bool $static = false, ?string $quote = null): Interpolation
|
||||
{
|
||||
if (!$this->quotes) {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
$quote = $quote ?? self::bestQuote($this->text->getContents());
|
||||
$buffer = new InterpolationBuffer();
|
||||
|
||||
$buffer->write($quote);
|
||||
|
||||
foreach ($this->text->getContents() as $value) {
|
||||
if ($value instanceof Expression) {
|
||||
$buffer->add($value);
|
||||
} else {
|
||||
$buffer->write(self::quoteInnerText($value, $quote, $static));
|
||||
}
|
||||
}
|
||||
|
||||
$buffer->write($quote);
|
||||
|
||||
return $buffer->buildInterpolation($this->text->getSpan());
|
||||
}
|
||||
|
||||
private static function quoteInnerText(string $value, string $quote, bool $static = false): string
|
||||
{
|
||||
$buffer = '';
|
||||
$length = \strlen($value);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = $value[$i];
|
||||
|
||||
if (Character::isNewline($char)) {
|
||||
$buffer .= '\\a';
|
||||
|
||||
if ($i !== $length - 1) {
|
||||
$next = $value[$i + 1];
|
||||
|
||||
if (Character::isWhitespace($next) || Character::isHex($next)) {
|
||||
$buffer .= ' ';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($char === $quote || $char === '\\' || ($static && $char === '#' && $i < $length - 1 && $value[$i + 1] === '{')) {
|
||||
$buffer .= '\\';
|
||||
}
|
||||
|
||||
if (\ord($char) < 0x80) {
|
||||
$buffer .= $char;
|
||||
} else {
|
||||
if (!preg_match('/./usA', $value, $m, 0, $i)) {
|
||||
throw new \UnexpectedValueException('Invalid UTF-8 char');
|
||||
}
|
||||
|
||||
$buffer .= $m[0];
|
||||
$i += \strlen($m[0]) - 1; // skip over the extra bytes that have been processed.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|Expression> $parts
|
||||
*/
|
||||
private static function bestQuote(array $parts): string
|
||||
{
|
||||
$containsDoubleQuote = false;
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if (!\is_string($part)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($part, "'")) {
|
||||
return '"';
|
||||
}
|
||||
|
||||
if (str_contains($part, '"')) {
|
||||
$containsDoubleQuote = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $containsDoubleQuote ? "'" : '"';
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->asInterpolation();
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An expression-level `@supports` condition.
|
||||
*
|
||||
* This appears only in the modifiers that come after a plain-CSS `@import`. It
|
||||
* doesn't include the function name wrapping the condition.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SupportsExpression implements Expression
|
||||
{
|
||||
private readonly SupportsCondition $condition;
|
||||
|
||||
public function __construct(SupportsCondition $condition)
|
||||
{
|
||||
$this->condition = $condition;
|
||||
}
|
||||
|
||||
public function getCondition(): SupportsCondition
|
||||
{
|
||||
return $this->condition;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->condition->getSpan();
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitSupportsExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->condition;
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A unary operator, as in `+$var` or `not fn()`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class UnaryOperationExpression implements Expression
|
||||
{
|
||||
private readonly UnaryOperator $operator;
|
||||
|
||||
private readonly Expression $operand;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(UnaryOperator $operator, Expression $operand, FileSpan $span)
|
||||
{
|
||||
$this->operator = $operator;
|
||||
$this->operand = $operand;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getOperator(): UnaryOperator
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
public function getOperand(): Expression
|
||||
{
|
||||
return $this->operand;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitUnaryOperationExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = $this->operator->getOperator();
|
||||
if ($this->operator === UnaryOperator::NOT) {
|
||||
$buffer .= ' ';
|
||||
}
|
||||
|
||||
$needsParens = $this->operand instanceof BinaryOperationExpression
|
||||
|| $this->operand instanceof UnaryOperationExpression
|
||||
|| ($this->operand instanceof ListExpression && !$this->operand->hasBrackets() && \count($this->operand->getContents()) > 1);
|
||||
|
||||
if ($needsParens) {
|
||||
$buffer .= '(';
|
||||
}
|
||||
|
||||
$buffer .= $this->operand;
|
||||
|
||||
if ($needsParens) {
|
||||
$buffer .= ')';
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
enum UnaryOperator
|
||||
{
|
||||
case PLUS;
|
||||
case MINUS;
|
||||
case DIVIDE;
|
||||
case NOT;
|
||||
|
||||
/**
|
||||
* The Sass syntax for this operator
|
||||
*/
|
||||
public function getOperator(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PLUS => '+',
|
||||
self::MINUS => '-',
|
||||
self::DIVIDE => '/',
|
||||
self::NOT => 'not',
|
||||
};
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Value\Value;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An expression that directly embeds a value.
|
||||
*
|
||||
* This is never constructed by the parser. It's only used when ASTs are
|
||||
* constructed dynamically, as for the `call()` function.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ValueExpression implements Expression
|
||||
{
|
||||
private readonly Value $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Value $value, FileSpan $span)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getValue(): Value
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitValueExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->value;
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassReference;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A Sass variable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class VariableExpression implements Expression, SassReference
|
||||
{
|
||||
/**
|
||||
* The name of this variable, with underscores converted to hyphens.
|
||||
*/
|
||||
private readonly string $name;
|
||||
|
||||
/**
|
||||
* The namespace of the variable being referenced, or `null` if it's
|
||||
* referenced without a namespace.
|
||||
*/
|
||||
private ?string $namespace;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $name, FileSpan $span, ?string $namespace = null)
|
||||
{
|
||||
$this->span = $span;
|
||||
$this->name = $name;
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getNamespace(): ?string
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
return SpanUtil::withoutNamespace($this->span);
|
||||
}
|
||||
|
||||
public function getNamespaceSpan(): ?FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($this->span);
|
||||
}
|
||||
|
||||
public function accept(ExpressionVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitVariableExpression($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->span->getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
/**
|
||||
* An interface for different types of import.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface Import extends SassNode
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Import;
|
||||
|
||||
use League\Uri\Contracts\UriInterface;
|
||||
use League\Uri\Uri;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression\StringExpression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Import;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An import that will load a Sass file at runtime.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DynamicImport implements Import
|
||||
{
|
||||
/**
|
||||
* The URI of the file to import.
|
||||
*
|
||||
* If this is relative, it's relative to the containing file.
|
||||
*/
|
||||
private readonly string $urlString;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $urlString, FileSpan $span)
|
||||
{
|
||||
$this->urlString = $urlString;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getUrl(): UriInterface
|
||||
{
|
||||
return Uri::new($this->urlString);
|
||||
}
|
||||
|
||||
public function getUrlString(): string
|
||||
{
|
||||
return $this->urlString;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return StringExpression::quoteText($this->urlString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Import;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Import;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An import that produces a plain CSS `@import` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class StaticImport implements Import
|
||||
{
|
||||
/**
|
||||
* The URL for this import.
|
||||
*
|
||||
* This already contains quotes.
|
||||
*/
|
||||
private readonly Interpolation $url;
|
||||
|
||||
/**
|
||||
* The modifiers (such as media or supports queries) attached to this import,
|
||||
* or `null` if none are attached.
|
||||
*/
|
||||
private readonly ?Interpolation $modifiers;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Interpolation $url, FileSpan $span, ?Interpolation $modifiers = null)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->span = $span;
|
||||
$this->modifiers = $modifiers;
|
||||
}
|
||||
|
||||
public function getUrl(): Interpolation
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getModifiers(): ?Interpolation
|
||||
{
|
||||
return $this->modifiers;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = (string) $this->url;
|
||||
|
||||
if ($this->modifiers !== null) {
|
||||
$buffer .= ' ' . $this->modifiers;
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Parser\InterpolationBuffer;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* Plain text interpolated with Sass expressions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Interpolation implements SassNode
|
||||
{
|
||||
/**
|
||||
* @var list<string|Expression>
|
||||
*/
|
||||
private readonly array $contents;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<string|Expression> $contents
|
||||
*/
|
||||
public function __construct(array $contents, FileSpan $span)
|
||||
{
|
||||
for ($i = 0; $i < \count($contents); $i++) {
|
||||
// Dart-sass has a validation on the type of elements here. This is useless for us because phpstan supports union types, unlike the Dart type system
|
||||
|
||||
if ($i != 0 && \is_string($contents[$i]) && \is_string($contents[$i - 1])) {
|
||||
throw new \InvalidArgumentException('The contents of an Interpolation may not contain adjacent strings.');
|
||||
}
|
||||
}
|
||||
|
||||
$this->contents = $contents;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string|Expression>
|
||||
*/
|
||||
public function getContents(): array
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this contains no interpolated expressions.
|
||||
*/
|
||||
public function isPlain(): bool
|
||||
{
|
||||
return $this->getAsPlain() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this contains no interpolated expressions, returns its text contents.
|
||||
*
|
||||
* Otherwise, returns `null`.
|
||||
*
|
||||
* @psalm-mutation-free
|
||||
*/
|
||||
public function getAsPlain(): ?string
|
||||
{
|
||||
if (\count($this->contents) === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (\count($this->contents) > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\is_string($this->contents[0])) {
|
||||
return $this->contents[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plain text before the interpolation, or the empty string.
|
||||
*/
|
||||
public function getInitialPlain(): string
|
||||
{
|
||||
$first = $this->contents[0] ?? null;
|
||||
|
||||
if (\is_string($first)) {
|
||||
return $first;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode('', array_map(fn($value) => \is_string($value) ? $value : '#{' . $value . '}', $this->contents));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A common interface for any node that declares a Sass member.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface SassDeclaration extends SassNode
|
||||
{
|
||||
/**
|
||||
* The name of the declaration, with underscores converted to hyphens.
|
||||
*
|
||||
* This does not include the `$` for variables.
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* The span containing this declaration's name.
|
||||
*
|
||||
* This includes the `$` for variables.
|
||||
*/
|
||||
public function getNameSpan(): FileSpan;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\AstNode;
|
||||
|
||||
/**
|
||||
* A node in the abstract syntax tree for an unevaluated Sass file.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface SassNode extends AstNode
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A common interface for any node that references a Sass member.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface SassReference extends SassNode
|
||||
{
|
||||
/**
|
||||
* The namespace of the member being referenced, or `null` if it's referenced
|
||||
* without a namespace.
|
||||
*/
|
||||
public function getNamespace(): ?string;
|
||||
|
||||
/**
|
||||
* The name of the member being referenced, with underscores converted to
|
||||
* hyphens.
|
||||
*
|
||||
* This does not include the `$` for variables.
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* The span containing this reference's name.
|
||||
*
|
||||
* For variables, this should include the `$`.
|
||||
*/
|
||||
public function getNameSpan(): FileSpan;
|
||||
|
||||
/**
|
||||
* The span containing this reference's namespace, null if {@see getNamespace} is
|
||||
* null.
|
||||
*/
|
||||
public function getNamespaceSpan(): ?FileSpan;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
|
||||
/**
|
||||
* A statement in a Sass syntax tree.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface Statement extends SassNode
|
||||
{
|
||||
/**
|
||||
* @template T
|
||||
* @param StatementVisitor<T> $visitor
|
||||
* @return T
|
||||
*/
|
||||
public function accept(StatementVisitor $visitor);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@at-root` rule.
|
||||
*
|
||||
* This moves it contents "up" the tree through parent nodes.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AtRootRule extends ParentStatement
|
||||
{
|
||||
private readonly ?Interpolation $query;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(array $children, FileSpan $span, ?Interpolation $query = null)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* The query specifying which statements this should move its contents through.
|
||||
*/
|
||||
public function getQuery(): ?Interpolation
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitAtRootRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '@at-root ';
|
||||
if ($this->query !== null) {
|
||||
$buffer .= $this->query . ' ';
|
||||
}
|
||||
|
||||
return $buffer . '{' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An unknown at-rule.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AtRule extends ParentStatement
|
||||
{
|
||||
private readonly Interpolation $name;
|
||||
|
||||
private readonly ?Interpolation $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[]|null $children
|
||||
*/
|
||||
public function __construct(Interpolation $name, FileSpan $span, ?Interpolation $value = null, ?array $children = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function getName(): Interpolation
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getValue(): ?Interpolation
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitAtRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '@' . $this->name;
|
||||
if ($this->value !== null) {
|
||||
$buffer .= ' ' . $this->value;
|
||||
}
|
||||
|
||||
$children = $this->getChildren();
|
||||
|
||||
if ($children === null) {
|
||||
return $buffer . ';';
|
||||
}
|
||||
|
||||
return $buffer . '{' . implode(' ', $children) . '}';
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An abstract class for callables (functions or mixins) that are declared in
|
||||
* user code.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class CallableDeclaration extends ParentStatement
|
||||
{
|
||||
private readonly string $name;
|
||||
|
||||
private readonly string $originalName;
|
||||
|
||||
private readonly ArgumentDeclaration $arguments;
|
||||
|
||||
private readonly ?SilentComment $comment;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(string $originalName, ArgumentDeclaration $arguments, FileSpan $span, array $children, ?SilentComment $comment = null)
|
||||
{
|
||||
$this->originalName = $originalName;
|
||||
$this->name = str_replace('_', '-', $originalName);
|
||||
$this->arguments = $arguments;
|
||||
$this->comment = $comment;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this callable, with underscores converted to hyphens.
|
||||
*/
|
||||
final public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The callable's original name, without underscores converted to hyphens.
|
||||
*/
|
||||
public function getOriginalName(): string
|
||||
{
|
||||
return $this->originalName;
|
||||
}
|
||||
|
||||
final public function getArguments(): ArgumentDeclaration
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
final public function getComment(): ?SilentComment
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
final public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An anonymous block of code that's invoked for a {@see ContentRule}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ContentBlock extends CallableDeclaration
|
||||
{
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(ArgumentDeclaration $arguments, array $children, FileSpan $span)
|
||||
{
|
||||
parent::__construct('@content', $arguments, $span, $children);
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitContentBlock($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = $this->getArguments()->isEmpty() ? '' : ' using (' . $this->getArguments() . ')';
|
||||
|
||||
return $buffer . '{' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@content` rule.
|
||||
*
|
||||
* This is used in a mixin to include statement-level content passed by the
|
||||
* caller.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ContentRule implements Statement
|
||||
{
|
||||
/**
|
||||
* The arguments pass to this `@content` rule.
|
||||
*
|
||||
* This will be an empty invocation if `@content` has no arguments.
|
||||
*/
|
||||
private readonly ArgumentInvocation $arguments;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(ArgumentInvocation $arguments, FileSpan $span)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getArguments(): ArgumentInvocation
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitContentRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->arguments->isEmpty() ? '@content;' : "@content($this->arguments);";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@debug` rule.
|
||||
*
|
||||
* This prints a Sass value for debugging purposes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DebugRule implements Statement
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Expression $expression, FileSpan $span)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitDebugRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@debug ' . $this->expression . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression\StringExpression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A declaration (that is, a `name: value` pair).
|
||||
*
|
||||
* @extends ParentStatement<Statement[]|null>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Declaration extends ParentStatement
|
||||
{
|
||||
private readonly Interpolation $name;
|
||||
|
||||
/**
|
||||
* The value of this declaration.
|
||||
*
|
||||
* If {@see getChildren} is `null`, this is never `null`. Otherwise, it may or may
|
||||
* not be `null`.
|
||||
*/
|
||||
private readonly ?Expression $value;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[]|null $children
|
||||
*/
|
||||
private function __construct(Interpolation $name, ?Expression $value, FileSpan $span, ?array $children = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public static function create(Interpolation $name, Expression $value, FileSpan $span): self
|
||||
{
|
||||
return new self($name, $value, $span);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public static function nested(Interpolation $name, array $children, FileSpan $span, ?Expression $value = null): self
|
||||
{
|
||||
return new self($name, $value, $span, $children);
|
||||
}
|
||||
|
||||
public function getName(): Interpolation
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getValue(): ?Expression
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a CSS Custom Property declaration.
|
||||
*
|
||||
* Note that this can return `false` for declarations that will ultimately be
|
||||
* serialized as custom properties if they aren't *parsed as* custom
|
||||
* properties, such as `#{--foo}: ...`.
|
||||
*
|
||||
* If this is `true`, then `value` will be a {@see StringExpression}.
|
||||
*/
|
||||
public function isCustomProperty(): bool
|
||||
{
|
||||
return str_starts_with($this->name->getInitialPlain(), '--');
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitDeclaration($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = $this->name . ':';
|
||||
|
||||
if ($this->value !== null) {
|
||||
if (!$this->isCustomProperty()) {
|
||||
$buffer .= ' ';
|
||||
}
|
||||
$buffer .= $this->value;
|
||||
}
|
||||
|
||||
$children = $this->getChildren();
|
||||
|
||||
if ($children === null) {
|
||||
return $buffer . ';';
|
||||
}
|
||||
|
||||
return $buffer . '{' . implode(' ', $children) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An `@each` rule.
|
||||
*
|
||||
* This iterates over values in a list or map.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EachRule extends ParentStatement
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private readonly array $variables;
|
||||
|
||||
private readonly Expression $list;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<string> $variables
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(array $variables, Expression $list, array $children, FileSpan $span)
|
||||
{
|
||||
$this->variables = $variables;
|
||||
$this->list = $list;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public function getVariables(): array
|
||||
{
|
||||
return $this->variables;
|
||||
}
|
||||
|
||||
public function getList(): Expression
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitEachRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@each ' . implode(', ', array_map(fn($variable) => '$' . $variable, $this->variables)) . ' in ' . $this->list . ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
/**
|
||||
* An `@else` clause in an `@if` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ElseClause extends IfRuleClause
|
||||
{
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@else {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@error` rule.
|
||||
*
|
||||
* This emits an error and stops execution.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ErrorRule implements Statement
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Expression $expression, FileSpan $span)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitErrorRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@error ' . $this->expression . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An `@extend` rule.
|
||||
*
|
||||
* This gives one selector all the styling of another.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ExtendRule implements Statement
|
||||
{
|
||||
private readonly Interpolation $selector;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
private readonly bool $optional;
|
||||
|
||||
public function __construct(Interpolation $selector, FileSpan $span, bool $optional = false)
|
||||
{
|
||||
$this->selector = $selector;
|
||||
$this->span = $span;
|
||||
$this->optional = $optional;
|
||||
}
|
||||
|
||||
public function getSelector(): Interpolation
|
||||
{
|
||||
return $this->selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is an optional extension.
|
||||
*
|
||||
* If an extension isn't optional, it will emit an error if it doesn't match
|
||||
* any selectors.
|
||||
*/
|
||||
public function isOptional(): bool
|
||||
{
|
||||
return $this->optional;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitExtendRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@extend ' . $this->selector . ($this->optional ? ' !optional' : '') . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@for` rule.
|
||||
*
|
||||
* This iterates a set number of times.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ForRule extends ParentStatement
|
||||
{
|
||||
private readonly string $variable;
|
||||
|
||||
private readonly Expression $from;
|
||||
|
||||
private readonly Expression $to;
|
||||
|
||||
private readonly bool $exclusive;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(string $variable, Expression $from, Expression $to, array $children, FileSpan $span, bool $exclusive = false)
|
||||
{
|
||||
$this->variable = $variable;
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->exclusive = $exclusive;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function getVariable(): string
|
||||
{
|
||||
return $this->variable;
|
||||
}
|
||||
|
||||
public function getFrom(): Expression
|
||||
{
|
||||
return $this->from;
|
||||
}
|
||||
|
||||
public function getTo(): Expression
|
||||
{
|
||||
return $this->to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether {@see getTo} is exclusive.
|
||||
*/
|
||||
public function isExclusive(): bool
|
||||
{
|
||||
return $this->exclusive;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitForRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@for $' . $this->variable . ' from ' . $this->from . ($this->exclusive ? ' to ' : ' through ') . $this->to . '{' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassDeclaration;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A function declaration.
|
||||
*
|
||||
* This declares a function that's invoked using normal CSS function syntax.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FunctionRule extends CallableDeclaration implements SassDeclaration
|
||||
{
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
return SpanUtil::initialIdentifier(SpanUtil::withoutInitialAtRule($this->getSpan()));
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitFunctionRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@function ' . $this->getName() . '(' . $this->getArguments() . ') {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Visitor\StatementSearchVisitor;
|
||||
|
||||
/**
|
||||
* A visitor for determining whether a {@see MixinRule} recursively contains a
|
||||
* {@see ContentRule}.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @extends StatementSearchVisitor<bool>
|
||||
*/
|
||||
final class HasContentVisitor extends StatementSearchVisitor
|
||||
{
|
||||
public function visitContentRule(ContentRule $node): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
/**
|
||||
* An `@if` or `@else if` clause in an `@if` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IfClause extends IfRuleClause
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(Expression $expression, array $children)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An `@if` rule.
|
||||
*
|
||||
* This conditionally executes a block of code.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IfRule implements Statement
|
||||
{
|
||||
/**
|
||||
* @var list<IfClause>
|
||||
*/
|
||||
private readonly array $clauses;
|
||||
|
||||
private readonly ?ElseClause $lastClause;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<IfClause> $clauses
|
||||
*/
|
||||
public function __construct(array $clauses, FileSpan $span, ?ElseClause $lastClause = null)
|
||||
{
|
||||
$this->clauses = $clauses;
|
||||
$this->span = $span;
|
||||
$this->lastClause = $lastClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `@if` and `@else if` clauses.
|
||||
*
|
||||
* The first clause whose expression evaluates to `true` will have its
|
||||
* statements executed. If no expression evaluates to `true`, `lastClause`
|
||||
* will be executed if it's not `null`.
|
||||
*
|
||||
* @return list<IfClause>
|
||||
*/
|
||||
public function getClauses(): array
|
||||
{
|
||||
return $this->clauses;
|
||||
}
|
||||
|
||||
/**
|
||||
* The final, unconditional `@else` clause.
|
||||
*
|
||||
* This is `null` if there is no unconditional `@else`.
|
||||
*/
|
||||
public function getLastClause(): ?ElseClause
|
||||
{
|
||||
return $this->lastClause;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitIfRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
foreach ($this->clauses as $index => $clause) {
|
||||
$parts[] = ($index === 0 ? '@if ' : '@else if ') . $clause->getExpression() . '{' . implode(' ', $clause->getChildren()) . '}';
|
||||
}
|
||||
|
||||
if ($this->lastClause !== null) {
|
||||
$parts[] = $this->lastClause;
|
||||
}
|
||||
|
||||
return implode(' ', $parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Import\DynamicImport;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Util\IterableUtil;
|
||||
|
||||
/**
|
||||
* The superclass of `@if` and `@else` clauses.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class IfRuleClause
|
||||
{
|
||||
/**
|
||||
* @var Statement[]
|
||||
*/
|
||||
private readonly array $children;
|
||||
|
||||
private readonly bool $declarations;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(array $children)
|
||||
{
|
||||
$this->children = $children;
|
||||
$this->declarations = IterableUtil::any($children, function (Statement $child) {
|
||||
if ($child instanceof VariableDeclaration || $child instanceof FunctionRule || $child instanceof MixinRule) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($child instanceof ImportRule) {
|
||||
return IterableUtil::any($child->getImports(), fn ($import) => $import instanceof DynamicImport);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Statement[]
|
||||
*/
|
||||
final public function getChildren(): array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
final public function hasDeclarations(): bool
|
||||
{
|
||||
return $this->declarations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Import;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* An `@import` rule.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ImportRule implements Statement
|
||||
{
|
||||
/**
|
||||
* @var list<Import>
|
||||
*/
|
||||
private readonly array $imports;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param list<Import> $imports
|
||||
*/
|
||||
public function __construct(array $imports, FileSpan $span)
|
||||
{
|
||||
$this->imports = $imports;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<Import>
|
||||
*/
|
||||
public function getImports(): array
|
||||
{
|
||||
return $this->imports;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitImportRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@import ' . implode(', ', $this->imports) . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\CallableInvocation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassReference;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A mixin invocation.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class IncludeRule implements Statement, CallableInvocation, SassReference
|
||||
{
|
||||
private readonly ?string $namespace;
|
||||
|
||||
private readonly string $name;
|
||||
|
||||
private readonly string $originalName;
|
||||
|
||||
private readonly ArgumentInvocation $arguments;
|
||||
|
||||
private readonly ?ContentBlock $content;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $originalName, ArgumentInvocation $arguments, FileSpan $span, ?string $namespace = null, ?ContentBlock $content = null)
|
||||
{
|
||||
$this->originalName = $originalName;
|
||||
$this->name = str_replace('_', '-', $originalName);
|
||||
$this->arguments = $arguments;
|
||||
$this->span = $span;
|
||||
$this->namespace = $namespace;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getNamespace(): ?string
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original name of the mixin being invoked, without underscores
|
||||
* converted to hyphens.
|
||||
*/
|
||||
public function getOriginalName(): string
|
||||
{
|
||||
return $this->originalName;
|
||||
}
|
||||
|
||||
public function getArguments(): ArgumentInvocation
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getContent(): ?ContentBlock
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function getSpanWithoutContent(): FileSpan
|
||||
{
|
||||
if ($this->content === null) {
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
return SpanUtil::trim($this->span->getFile()->span($this->span->getStart()->getOffset(), $this->arguments->getSpan()->getEnd()->getOffset()));
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
$startSpan = $this->span->getText()[0] === '+' ? SpanUtil::trimLeft($this->span->subspan(1)) : SpanUtil::withoutInitialAtRule($this->span);
|
||||
|
||||
if ($this->namespace !== null) {
|
||||
$startSpan = SpanUtil::withoutNamespace($startSpan);
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($startSpan);
|
||||
}
|
||||
|
||||
public function getNamespaceSpan(): ?FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$startSpan = $this->span->getText()[0] === '+'
|
||||
? SpanUtil::trimLeft($this->span->subspan(1))
|
||||
: SpanUtil::withoutInitialAtRule($this->span);
|
||||
|
||||
return SpanUtil::initialIdentifier($startSpan);
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitIncludeRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '@include ';
|
||||
|
||||
if ($this->namespace !== null) {
|
||||
$buffer .= $this->namespace . '.';
|
||||
}
|
||||
$buffer .= $this->name;
|
||||
|
||||
if (!$this->arguments->isEmpty()) {
|
||||
$buffer .= "($this->arguments)";
|
||||
}
|
||||
|
||||
$buffer .= $this->content === null ? ';' : ' ' . $this->content;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A loud CSS-style comment.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class LoudComment implements Statement
|
||||
{
|
||||
private readonly Interpolation $text;
|
||||
|
||||
public function __construct(Interpolation $text)
|
||||
{
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
public function getText(): Interpolation
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->text->getSpan();
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitLoudComment($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) $this->text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@media` rule.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MediaRule extends ParentStatement
|
||||
{
|
||||
private readonly Interpolation $query;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(Interpolation $query, array $children, FileSpan $span)
|
||||
{
|
||||
$this->query = $query;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* The query that determines on which platforms the styles will be in effect.
|
||||
*
|
||||
* This is only parsed after the interpolation has been resolved.
|
||||
*/
|
||||
public function getQuery(): Interpolation
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitMediaRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@media ' . $this->query . ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\ArgumentDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A mixin declaration.
|
||||
*
|
||||
* This declares a mixin that's invoked using `@include`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MixinRule extends CallableDeclaration implements SassDeclaration
|
||||
{
|
||||
/**
|
||||
* Whether the mixin contains a `@content` rule.
|
||||
*/
|
||||
private ?bool $content = null;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(string $name, ArgumentDeclaration $arguments, FileSpan $span, array $children, ?SilentComment $comment = null)
|
||||
{
|
||||
parent::__construct($name, $arguments, $span, $children, $comment);
|
||||
}
|
||||
|
||||
public function hasContent(): bool
|
||||
{
|
||||
if (!isset($this->content)) {
|
||||
$this->content = (new HasContentVisitor())->visitMixinRule($this) === true;
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
$startSpan = $this->getSpan()->getText()[0] === '='
|
||||
? SpanUtil::trimLeft($this->getSpan()->subspan(1))
|
||||
: SpanUtil::withoutInitialAtRule($this->getSpan());
|
||||
|
||||
return SpanUtil::initialIdentifier($startSpan);
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitMixinRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '@mixin ' . $this->getName();
|
||||
|
||||
if (!$this->getArguments()->isEmpty()) {
|
||||
$buffer .= "({$this->getArguments()})";
|
||||
}
|
||||
|
||||
$buffer .= ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Import\DynamicImport;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
/**
|
||||
* A {@see Statement} that can have child statements.
|
||||
*
|
||||
* This has a generic parameter so that its subclasses can choose whether or
|
||||
* not their children lists are nullable.
|
||||
*
|
||||
* @template T
|
||||
* @psalm-template T of (Statement[]|null)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class ParentStatement implements Statement
|
||||
{
|
||||
/**
|
||||
* @var T
|
||||
*/
|
||||
private readonly ?array $children;
|
||||
|
||||
private readonly bool $declarations;
|
||||
|
||||
/**
|
||||
* @param T $children
|
||||
*/
|
||||
public function __construct(?array $children)
|
||||
{
|
||||
$this->children = $children;
|
||||
|
||||
if ($children === null) {
|
||||
$this->declarations = false;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($children as $child) {
|
||||
if ($child instanceof VariableDeclaration || $child instanceof FunctionRule || $child instanceof MixinRule) {
|
||||
$this->declarations = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($child instanceof ImportRule) {
|
||||
foreach ($child->getImports() as $import) {
|
||||
if ($import instanceof DynamicImport) {
|
||||
$this->declarations = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->declarations = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
final public function getChildren(): ?array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
final public function hasDeclarations(): bool
|
||||
{
|
||||
return $this->declarations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@return` rule.
|
||||
*
|
||||
* This exits from the current function body with a return value.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ReturnRule implements Statement
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Expression $expression, FileSpan $span)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitReturnRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@return ' . $this->expression . ';';
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A silent Sass-style comment.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SilentComment implements Statement
|
||||
{
|
||||
private readonly string $text;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $text, FileSpan $span)
|
||||
{
|
||||
$this->text = $text;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitSilentComment($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A style rule.
|
||||
*
|
||||
* This applies style declarations to elements that match a given selector.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class StyleRule extends ParentStatement
|
||||
{
|
||||
private readonly Interpolation $selector;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(Interpolation $selector, array $children, FileSpan $span)
|
||||
{
|
||||
$this->selector = $selector;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
/**
|
||||
* The selector to which the declaration will be applied.
|
||||
*
|
||||
* This is only parsed after the interpolation has been resolved.
|
||||
*/
|
||||
public function getSelector(): Interpolation
|
||||
{
|
||||
return $this->selector;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitStyleRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->selector . ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use League\Uri\Contracts\UriInterface;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Exception\SassFormatException;
|
||||
use ScssPhp\ScssPhp\Logger\LoggerInterface;
|
||||
use ScssPhp\ScssPhp\Parser\CssParser;
|
||||
use ScssPhp\ScssPhp\Parser\SassParser;
|
||||
use ScssPhp\ScssPhp\Parser\ScssParser;
|
||||
use ScssPhp\ScssPhp\Syntax;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A Sass stylesheet.
|
||||
*
|
||||
* This is the root Sass node. It contains top-level statements.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Stylesheet extends ParentStatement
|
||||
{
|
||||
private readonly bool $plainCss;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(array $children, FileSpan $span, bool $plainCss = false)
|
||||
{
|
||||
$this->span = $span;
|
||||
$this->plainCss = $plainCss;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function isPlainCss(): bool
|
||||
{
|
||||
return $this->plainCss;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitStylesheet($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SassFormatException when parsing fails
|
||||
*/
|
||||
public static function parse(string $contents, Syntax $syntax, ?LoggerInterface $logger = null, ?UriInterface $sourceUrl = null): self
|
||||
{
|
||||
return match ($syntax) {
|
||||
Syntax::SASS => self::parseSass($contents, $logger, $sourceUrl),
|
||||
Syntax::SCSS => self::parseScss($contents, $logger, $sourceUrl),
|
||||
Syntax::CSS => self::parseCss($contents, $logger, $sourceUrl),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SassFormatException when parsing fails
|
||||
*/
|
||||
public static function parseSass(string $contents, ?LoggerInterface $logger = null, ?UriInterface $sourceUrl = null): self
|
||||
{
|
||||
return (new SassParser($contents, $logger, $sourceUrl))->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SassFormatException when parsing fails
|
||||
*/
|
||||
public static function parseScss(string $contents, ?LoggerInterface $logger = null, ?UriInterface $sourceUrl = null): self
|
||||
{
|
||||
return (new ScssParser($contents, $logger, $sourceUrl))->parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SassFormatException when parsing fails
|
||||
*/
|
||||
public static function parseCss(string $contents, ?LoggerInterface $logger = null, ?UriInterface $sourceUrl = null): self
|
||||
{
|
||||
return (new CssParser($contents, $logger, $sourceUrl))->parse();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return implode(' ', $this->getChildren());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@supports` rule.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SupportsRule extends ParentStatement
|
||||
{
|
||||
private readonly SupportsCondition $condition;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(SupportsCondition $condition, array $children, FileSpan $span)
|
||||
{
|
||||
$this->condition = $condition;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function getCondition(): SupportsCondition
|
||||
{
|
||||
return $this->condition;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitSupportsRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@supports ' . $this->condition . ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\SassDeclaration;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Util;
|
||||
use ScssPhp\ScssPhp\Util\SpanUtil;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A variable declaration.
|
||||
*
|
||||
* This defines or sets a variable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class VariableDeclaration implements Statement, SassDeclaration
|
||||
{
|
||||
private readonly ?string $namespace;
|
||||
|
||||
private readonly string $name;
|
||||
|
||||
private readonly ?SilentComment $comment;
|
||||
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly bool $guarded;
|
||||
|
||||
private readonly bool $global;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(string $name, Expression $expression, FileSpan $span, ?string $namespace = null, bool $guarded = false, bool $global = false, ?SilentComment $comment = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
$this->namespace = $namespace;
|
||||
$this->guarded = $guarded;
|
||||
$this->global = $global;
|
||||
$this->comment = $comment;
|
||||
|
||||
if ($namespace !== null && $global) {
|
||||
throw new \InvalidArgumentException("Other modules' members can't be defined with !global.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getNamespace(): ?string
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the variable, with underscores converted to hyphens.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The variable name as written in the document, without underscores
|
||||
* converted to hyphens and including the leading `$`.
|
||||
*
|
||||
* This isn't particularly efficient, and should only be used for error
|
||||
* messages.
|
||||
*/
|
||||
public function getOriginalName(): string
|
||||
{
|
||||
return Util::declarationName($this->span);
|
||||
}
|
||||
|
||||
public function getComment(): ?SilentComment
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function isGuarded(): bool
|
||||
{
|
||||
return $this->guarded;
|
||||
}
|
||||
|
||||
public function isGlobal(): bool
|
||||
{
|
||||
return $this->global;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function getNameSpan(): FileSpan
|
||||
{
|
||||
$span = $this->span;
|
||||
|
||||
if ($this->namespace !== null) {
|
||||
$span = SpanUtil::withoutNamespace($span);
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($span, 1);
|
||||
}
|
||||
|
||||
public function getNamespaceSpan(): ?FileSpan
|
||||
{
|
||||
if ($this->namespace === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SpanUtil::initialIdentifier($this->span);
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitVariableDeclaration($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
$buffer = '';
|
||||
if ($this->namespace !== null) {
|
||||
$buffer .= $this->namespace . '.';
|
||||
}
|
||||
$buffer .= "\$$this->name: $this->expression;";
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@warn` rule.
|
||||
*
|
||||
* This prints a Sass value—usually a string—to warn the user of something.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WarnRule implements Statement
|
||||
{
|
||||
private readonly Expression $expression;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
public function __construct(Expression $expression, FileSpan $span)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->span = $span;
|
||||
}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitWarnRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@warn ' . $this->expression . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2020 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://scssphp.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Expression;
|
||||
use ScssPhp\ScssPhp\Ast\Sass\Statement;
|
||||
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
|
||||
use SourceSpan\FileSpan;
|
||||
|
||||
/**
|
||||
* A `@while` rule.
|
||||
*
|
||||
* This repeatedly executes a block of code as long as a statement evaluates to
|
||||
* `true`.
|
||||
*
|
||||
* @extends ParentStatement<Statement[]>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WhileRule extends ParentStatement
|
||||
{
|
||||
private readonly Expression $condition;
|
||||
|
||||
private readonly FileSpan $span;
|
||||
|
||||
/**
|
||||
* @param Statement[] $children
|
||||
*/
|
||||
public function __construct(Expression $condition, array $children, FileSpan $span)
|
||||
{
|
||||
$this->condition = $condition;
|
||||
$this->span = $span;
|
||||
parent::__construct($children);
|
||||
}
|
||||
|
||||
public function getCondition(): Expression
|
||||
{
|
||||
return $this->condition;
|
||||
}
|
||||
|
||||
public function getSpan(): FileSpan
|
||||
{
|
||||
return $this->span;
|
||||
}
|
||||
|
||||
public function accept(StatementVisitor $visitor)
|
||||
{
|
||||
return $visitor->visitWhileRule($this);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return '@while ' . $this->condition . ' {' . implode(' ', $this->getChildren()) . '}';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user