first commit

This commit is contained in:
2026-03-05 13:07:40 +01:00
commit 64ba0721ee
25709 changed files with 4691006 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<?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 ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* A node in an abstract syntax tree.
*
* @internal
*/
interface AstNode
{
public function getSpan(): FileSpan;
public function __toString(): string;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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\Css;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\Value;
/**
* 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;
/**
* 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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,258 @@
<?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\Exception\SassFormatException;
use ScssPhp\ScssPhp\Logger\LoggerInterface;
use ScssPhp\ScssPhp\Parser\MediaQueryParser;
/**
* A plain CSS media query, as used in `@media` and `@import`.
*
* @internal
*/
final class CssMediaQuery
{
public const MERGE_RESULT_EMPTY = 'empty';
public const MERGE_RESULT_UNREPRESENTABLE = 'unrepresentable';
/**
* The modifier, probably either "not" or "only".
*
* This may be `null` if no modifier is in use.
*
* @var string|null
* @readonly
*/
private $modifier;
/**
* The media type, for example "screen" or "print".
*
* This may be `null`. If so, {@see $conditions} will not be empty.
*
* @var string|null
* @readonly
*/
private $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`.
*
* @var bool
* @readonly
*/
private $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>
* @readonly
*/
private $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, ?string $url = null): array
{
return (new MediaQueryParser($contents, $logger, $url))->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.
*
* @return CssMediaQuery|string
* @phpstan-return CssMediaQuery|CssMediaQuery::*
*/
public function merge(CssMediaQuery $other)
{
if (!$this->conjunction || !$other->conjunction) {
return self::MERGE_RESULT_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 self::MERGE_RESULT_EMPTY;
}
return self::MERGE_RESULT_UNREPRESENTABLE;
}
if ($this->matchesAllTypes() || $other->matchesAllTypes()) {
return self::MERGE_RESULT_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 self::MERGE_RESULT_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 self::MERGE_RESULT_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 self::MERGE_RESULT_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
);
}
}

View File

@@ -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;
}

View File

@@ -0,0 +1,63 @@
<?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
{
/**
* 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($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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,39 @@
<?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.
*
* @return CssValue<SelectorList>
*/
public function getSelector(): CssValue;
/**
* The selector for this rule, before any extensions were applied.
*/
public function getOriginalSelector(): SelectorList;
}

View File

@@ -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
{
}

View File

@@ -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;
}

View File

@@ -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\Ast\AstNode;
use ScssPhp\ScssPhp\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.
*
* @template T
*
* @internal
*/
class CssValue implements AstNode
{
/**
* @phpstan-var T
*/
protected $value;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param T $value
*/
public function __construct($value, FileSpan $span)
{
$this->value = $value;
$this->span = $span;
}
/**
* @return T
*/
public function getValue()
{
return $this->value;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function __toString(): string
{
if (\is_array($this->value)) {
return implode($this->value);
}
return (string) $this->value;
}
}

View File

@@ -0,0 +1,63 @@
<?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.
*
* @var bool
* @readonly
*/
private $includeBogus;
/**
* Whether to consider comments invisible.
*
* @var bool
* @readonly
*/
private $includeComments;
public function __construct(bool $includeBogus, bool $includeComments)
{
$this->includeBogus = $includeBogus;
$this->includeComments = $includeComments;
}
public function visitCssAtRule($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($node): bool
{
return $this->includeComments && !$node->isPreserved();
}
public function visitCssStyleRule($node): bool
{
return ($this->includeBogus ? $node->getSelector()->getValue()->isInvisible() : $node->getSelector()->getValue()->isInvisibleOtherThanBogusCombinators()) || parent::visitCssStyleRule($node);
}
}

View File

@@ -0,0 +1,103 @@
<?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\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 $name;
/**
* @var CssValue<string>|null
*/
private $value;
/**
* @var bool
* @readonly
*/
private $childless;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssValue<string> $name
* @param CssValue<string>|null $value
* @param bool $childless
* @param FileSpan $span
*/
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($visitor)
{
return $visitor->visitCssAtRule($this);
}
/**
* @phpstan-return ModifiableCssAtRule
*/
public function copyWithoutChildren(): ModifiableCssParentNode
{
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);
}
}

View File

@@ -0,0 +1,61 @@
<?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\SourceSpan\FileSpan;
/**
* A modifiable version of {@see CssComment} for use in the evaluation step.
*
* @internal
*/
final class ModifiableCssComment extends ModifiableCssNode implements CssComment
{
/**
* @var string
* @readonly
*/
private $text;
/**
* @var FileSpan
* @readonly
*/
private $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($visitor)
{
return $visitor->visitCssComment($this);
}
}

View File

@@ -0,0 +1,115 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\SassString;
use ScssPhp\ScssPhp\Value\Value;
/**
* A modifiable version of {@see CssDeclaration} for use in the evaluation step.
*
* @internal
*/
final class ModifiableCssDeclaration extends ModifiableCssNode implements CssDeclaration
{
/**
* @var CssValue<string>
* @readonly
*/
private $name;
/**
* @var CssValue<Value>
* @readonly
*/
private $value;
/**
* @var bool
* @readonly
*/
private $parsedAsCustomProperty;
/**
* @var FileSpan
* @readonly
*/
private $valueSpanForMap;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssValue<string> $name
* @param CssValue<Value> $value
* @param bool $parsedAsCustomProperty
* @param FileSpan $valueSpanForMap
* @param FileSpan $span
*/
public function __construct(CssValue $name, CssValue $value, FileSpan $span, bool $parsedAsCustomProperty, ?FileSpan $valueSpanForMap = null) {
$this->name = $name;
$this->value = $value;
$this->parsedAsCustomProperty = $parsedAsCustomProperty;
$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_class($value->getValue())));
}
}
}
public function getName(): CssValue
{
return $this->name;
}
public function getValue(): CssValue
{
return $this->value;
}
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 0 === strpos($this->name->getValue(), '--');
}
public function accept($visitor)
{
return $visitor->visitCssDeclaration($this);
}
}

View File

@@ -0,0 +1,77 @@
<?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\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>
* @readonly
*/
private $url;
/**
* @var CssValue<string>|null
* @readonly
*/
private $modifiers;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssValue<string> $url
* @param FileSpan $span
* @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($visitor)
{
return $visitor->visitCssImport($this);
}
}

View File

@@ -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\Css;
use ScssPhp\ScssPhp\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>>
* @readonly
*/
private $selector;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssValue<list<string>> $selector
* @param FileSpan $span
*/
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($visitor)
{
return $visitor->visitCssKeyframeBlock($this);
}
/**
* @phpstan-return ModifiableCssKeyframeBlock
*/
public function copyWithoutChildren(): ModifiableCssParentNode
{
return new ModifiableCssKeyframeBlock($this->selector, $this->span);
}
}

View File

@@ -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\Css;
use ScssPhp\ScssPhp\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 $queries;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssMediaQuery[] $queries
* @param FileSpan $span
*/
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($visitor)
{
return $visitor->visitCssMediaRule($this);
}
public function copyWithoutChildren(): ModifiableCssParentNode
{
return new ModifiableCssMediaRule($this->queries, $this->span);
}
}

View File

@@ -0,0 +1,151 @@
<?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
{
/**
* @var ModifiableCssParentNode|null
*/
private $parent;
/**
* The index of `$this` in parent's children.
*
* This makes {@see remove} more efficient.
*
* @var int|null
*/
private $indexInParent;
/**
* @var bool
*/
private $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($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;
}
public function __toString(): string
{
return Serializer::serialize($this, true)->getCss();
}
}

View File

@@ -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\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 $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 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);
}
}

View File

@@ -0,0 +1,86 @@
<?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\SourceSpan\FileSpan;
/**
* A modifiable version of {@see CssStyleRule} for use in the evaluation step.
*
* @internal
*/
final class ModifiableCssStyleRule extends ModifiableCssParentNode implements CssStyleRule
{
/**
* @var ModifiableCssValue<SelectorList>
* @readonly
*/
private $selector;
/**
* @var SelectorList
* @readonly
*/
private $originalSelector;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param ModifiableCssValue<SelectorList> $selector
* @param FileSpan $span
* @param SelectorList|null $originalSelector
*/
public function __construct(ModifiableCssValue $selector, FileSpan $span, ?SelectorList $originalSelector = null)
{
parent::__construct();
$this->selector = $selector;
$this->originalSelector = $originalSelector ?? $selector->getValue();
$this->span = $span;
}
/**
* @phpstan-return ModifiableCssValue<SelectorList>
*/
public function getSelector(): CssValue
{
return $this->selector;
}
public function getOriginalSelector(): SelectorList
{
return $this->originalSelector;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function accept($visitor)
{
return $visitor->visitCssStyleRule($this);
}
/**
* @phpstan-return ModifiableCssStyleRule
*/
public function copyWithoutChildren(): ModifiableCssParentNode
{
return new ModifiableCssStyleRule($this->selector, $this->span, $this->originalSelector);
}
}

View File

@@ -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\SourceSpan\FileSpan;
/**
* A modifiable version of {@see CssStylesheet} for use in the evaluation step.
*
* @internal
*/
final class ModifiableCssStylesheet extends ModifiableCssParentNode implements CssStylesheet
{
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param 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($visitor)
{
return $visitor->visitCssStylesheet($this);
}
/**
* @phpstan-return ModifiableCssStylesheet
*/
public function copyWithoutChildren(): ModifiableCssParentNode
{
return new ModifiableCssStylesheet($this->span);
}
}

View File

@@ -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\Css;
use ScssPhp\ScssPhp\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>
* @readonly
*/
private $condition;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param CssValue<string> $condition
* @param FileSpan $span
*/
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($visitor)
{
return $visitor->visitCssSupportsRule($this);
}
/**
* @phpstan-return ModifiableCssSupportsRule
*/
public function copyWithoutChildren(): ModifiableCssParentNode
{
return new ModifiableCssSupportsRule($this->condition, $this->span);
}
}

View File

@@ -0,0 +1,32 @@
<?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 CssValue} for use in the evaluation step.
*
* @template T
* @template-extends CssValue<T>
*
* @internal
*/
final class ModifiableCssValue extends CssValue
{
/**
* @param T $value
*/
public function setValue($value): void
{
$this->value = $value;
}
}

View File

@@ -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;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* An {@see AstNode} that just exposes a single span generated by a callback.
*
* @internal
*/
final class FakeAstNode implements AstNode
{
/**
* @var callable(): FileSpan
* @readonly
*/
private $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 '';
}
}

View File

@@ -0,0 +1,99 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util;
use ScssPhp\ScssPhp\Util\SpanUtil;
/**
* An argument declared as part of an {@see ArgumentDeclaration}.
*
* @internal
*/
final class Argument implements SassNode, SassDeclaration
{
/**
* @var string
* @readonly
*/
private $name;
/**
* @var Expression|null
* @readonly
*/
private $defaultValue;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,228 @@
<?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\Exception\SassFormatException;
use ScssPhp\ScssPhp\Exception\SassScriptException;
use ScssPhp\ScssPhp\Logger\LoggerInterface;
use ScssPhp\ScssPhp\Parser\ScssParser;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* An argument declaration, as for a function or mixin definition.
*
* @internal
*/
final class ArgumentDeclaration implements SassNode
{
/**
* @var list<Argument>
* @readonly
*/
private $arguments;
/**
* @var string|null
* @readonly
*/
private $restArgument;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param list<Argument> $arguments
* @param FileSpan $span
* @param string|null $restArgument
*/
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, ?string $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;
}
/**
* @param int $positional
* @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 SassScriptException(sprintf('Missing argument $%s', $originalName));
}
}
if ($this->restArgument !== null) {
return;
}
if ($positional > \count($this->arguments)) {
$message = sprintf(
'Only %d %sargument%s allowed, but %d %s passed.',
\count($this->arguments),
empty($names) ? '' : 'positional ',
\count($this->arguments) === 1 ? '' : 's',
$positional,
$positional === 1 ? 'was' : 'were'
);
throw new SassScriptException($message);
}
if ($nameUsed < \count($names)) {
$unknownNames = array_values(array_diff(array_keys($names), array_map(function ($argument) {
return $argument->getName();
}, $this->arguments)));
$lastName = array_pop($unknownNames);
$message = sprintf(
'No argument%s named $%s%s.',
$unknownNames ? 's' : '',
$unknownNames ? implode(', $', $unknownNames) . ' or $' : '',
$lastName
);
throw new SassScriptException($message);
}
}
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 int $positional
* @param array<string, mixed> $names Only keys are relevant
*
* @return bool
*/
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);
}
}

View File

@@ -0,0 +1,127 @@
<?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\SourceSpan\FileSpan;
/**
* A set of arguments passed in to a function or mixin.
*
* @internal
*/
final class ArgumentInvocation implements SassNode
{
/**
* @var list<Expression>
* @readonly
*/
private $positional;
/**
* @var array<string, Expression>
* @readonly
*/
private $named;
/**
* @var Expression|null
* @readonly
*/
private $rest;
/**
* @var Expression|null
*/
private $keywordRest;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param list<Expression> $positional
* @param array<string, Expression> $named
* @param FileSpan $span
* @param Expression|null $rest
* @param Expression|null $keywordRest
*/
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 = $this->positional;
foreach ($this->named as $name => $arg) {
$parts[] = "\$$name: $arg";
}
if ($this->rest !== null) {
$parts[] = "$this->rest...";
}
if ($this->keywordRest !== null) {
$parts[] = "$this->keywordRest...";
}
return '(' . implode(', ', $parts) . ')';
}
}

View File

@@ -0,0 +1,167 @@
<?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\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;
/**
* A query for the `@at-root` rule.
*
* @internal
*/
final class AtRootQuery
{
/**
* Whether the query includes or excludes rules with the specified names.
*
* @var bool
* @readonly
*/
private $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[]
* @readonly
*/
private $names;
/**
* Whether this includes or excludes *all* rules.
*
* @var bool
* @readonly
*/
private $all;
/**
* Whether this includes or excludes style rules.
*
* @var bool
* @readonly
*/
private $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, ?string $url = null): AtRootQuery
{
return (new AtRootQueryParser($contents, $logger, $url))->parse();
}
/**
* @param string[] $names
* @param bool $include
*/
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
* @param bool $include
* @param bool $all
* @param bool $rule
*/
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;
}
}

View File

@@ -0,0 +1,18 @@
<?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;
interface CallableInvocation extends SassNode
{
public function getArguments(): ArgumentInvocation;
}

View File

@@ -0,0 +1,86 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
/**
* A variable configured by a `with` clause in a `@use` or `@forward` rule.
*
* @internal
*/
final class ConfiguredVariable implements SassNode, SassDeclaration
{
/**
* @var string
* @readonly
*/
private $name;
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @var bool
* @readonly
*/
private $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' : '');
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,148 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A binary operator, as in `1 + 2` or `$this and $other`.
*
* @internal
*/
final class BinaryOperationExpression implements Expression
{
/**
* @var BinaryOperator::*
* @readonly
*/
private $operator;
/**
* @var Expression
* @readonly
*/
private $left;
/**
* @var Expression
* @readonly
*/
private $right;
/**
* Whether this is a dividedBy operation that may be interpreted as slash-separated numbers.
*
* @var bool
*/
private $allowsSlash = false;
/**
* @param BinaryOperator::* $operator
*/
public function __construct(string $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;
}
/**
* @return BinaryOperator::*
*/
public function getOperator(): string
{
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);
}
public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitBinaryOperationExpression($this);
}
public function __toString(): string
{
$buffer = '';
$leftNeedsParens = $this->left instanceof BinaryOperationExpression && BinaryOperator::getPrecedence($this->left->getOperator()) < BinaryOperator::getPrecedence($this->operator);
if ($leftNeedsParens) {
$buffer .= '(';
}
$buffer .= $this->left;
if ($leftNeedsParens) {
$buffer .= ')';
}
$buffer .= ' ';
$buffer .= $this->operator;
$buffer .= ' ';
$rightNeedsParens = $this->right instanceof BinaryOperationExpression && BinaryOperator::getPrecedence($this->right->getOperator()) <= BinaryOperator::getPrecedence($this->operator);
if ($rightNeedsParens) {
$buffer .= '(';
}
$buffer .= $this->right;
if ($rightNeedsParens) {
$buffer .= ')';
}
return $buffer;
}
}

View File

@@ -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\Expression;
/**
* @internal
*/
final class BinaryOperator
{
const SINGLE_EQUALS = '=';
const OR = 'or';
const AND = 'and';
const EQUALS = '==';
const NOT_EQUALS = '!=';
const GREATER_THAN = '>';
const GREATER_THAN_OR_EQUALS = '>=';
const LESS_THAN = '<';
const LESS_THAN_OR_EQUALS = '<=';
const PLUS = '+';
const MINUS = '-';
const TIMES = '*';
const DIVIDED_BY = '/';
const MODULO = '%';
/**
* @param BinaryOperator::* $operator
*/
public static function getPrecedence(string $operator): int
{
switch ($operator) {
case self::SINGLE_EQUALS:
return 0;
case self::OR:
return 1;
case self::AND:
return 2;
case self::EQUALS:
case self::NOT_EQUALS:
return 3;
case self::GREATER_THAN:
case self::GREATER_THAN_OR_EQUALS:
case self::LESS_THAN:
case self::LESS_THAN_OR_EQUALS:
return 4;
case self::PLUS:
case self::MINUS:
return 5;
case self::TIMES:
case self::DIVIDED_BY:
case self::MODULO:
return 6;
}
throw new \InvalidArgumentException(sprintf('Unknown operator "%s".', $operator));
}
}

View File

@@ -0,0 +1,63 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A boolean literal, `true` or `false`.
*
* @internal
*/
final class BooleanExpression implements Expression
{
/**
* @var bool
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $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';
}
}

View File

@@ -0,0 +1,235 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A calculation literal.
*
* @internal
*/
final class CalculationExpression implements Expression
{
/**
* This calculation's name.
*
* @var string
* @readonly
*/
private $name;
/**
* The arguments for the calculation.
*
* @var list<Expression>
* @readonly
*/
private $arguments;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* Returns a `calc()` calculation expression.
*
* @param Expression $argument
* @param FileSpan $span
*
* @return CalculationExpression
*/
public static function calc(Expression $argument, FileSpan $span): CalculationExpression
{
return new CalculationExpression('calc', [$argument], $span);
}
/**
* Returns a `min()` calculation expression.
*
* @param list<Expression> $arguments
* @param FileSpan $span
*
* @return CalculationExpression
*/
public static function min(array $arguments, FileSpan $span): CalculationExpression
{
if (!$arguments) {
throw new \InvalidArgumentException('min() requires at least one argument.');
}
return new CalculationExpression('min', $arguments, $span);
}
/**
* Returns a `max()` calculation expression.
*
* @param list<Expression> $arguments
* @param FileSpan $span
*
* @return CalculationExpression
*/
public static function max(array $arguments, FileSpan $span): CalculationExpression
{
if (!$arguments) {
throw new \InvalidArgumentException('max() requires at least one argument.');
}
return new CalculationExpression('max', $arguments, $span);
}
/**
* Returns a `clamp()` calculation expression.
*
* @param Expression $min
* @param Expression $value
* @param Expression $max
* @param FileSpan $span
*
* @return CalculationExpression
*/
public static function clamp(Expression $min, Expression $value, Expression $max, FileSpan $span): CalculationExpression
{
return new CalculationExpression('clamp', [$min, $value, $max], $span);
}
/**
* Returns a calculation expression with the given name and arguments.
*
* Unlike the other constructors, this doesn't verify that the arguments are
* valid for the name.
*
* @param string $name
* @param list<Expression> $arguments
* @param FileSpan $span
*/
public function __construct(string $name, array $arguments, FileSpan $span)
{
self::verifyArguments($arguments);
$this->name = $name;
$this->arguments = $arguments;
$this->span = $span;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return list<Expression>
*/
public function getArguments(): array
{
return $this->arguments;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitCalculationExpression($this);
}
/**
* @param list<Expression> $arguments
*
* @throws \InvalidArgumentException if $arguments aren't valid calculation arguments.
*/
private static function verifyArguments(array $arguments): void
{
foreach ($arguments as $argument) {
self::verify($argument);
}
}
/**
* @throws \InvalidArgumentException if $expression isn't a valid calculation argument.
*/
private static function verify(Expression $expression): void
{
if ($expression instanceof NumberExpression) {
return;
}
if ($expression instanceof CalculationExpression) {
return;
}
if ($expression instanceof VariableExpression) {
return;
}
if ($expression instanceof FunctionExpression) {
return;
}
if ($expression instanceof IfExpression) {
return;
}
if ($expression instanceof StringExpression) {
if ($expression->hasQuotes()) {
throw new \InvalidArgumentException('Invalid calculation argument.');
}
return;
}
if ($expression instanceof ParenthesizedExpression) {
self::verify($expression->getExpression());
return;
}
if ($expression instanceof BinaryOperationExpression) {
self::verify($expression->getLeft());
self::verify($expression->getRight());
if ($expression->getOperator() === BinaryOperator::PLUS) {
return;
}
if ($expression->getOperator() === BinaryOperator::MINUS) {
return;
}
if ($expression->getOperator() === BinaryOperator::TIMES) {
return;
}
if ($expression->getOperator() === BinaryOperator::DIVIDED_BY) {
return;
}
throw new \InvalidArgumentException('Invalid calculation argument.');
}
throw new \InvalidArgumentException('Invalid calculation argument.');
}
public function __toString(): string
{
return $this->name . '(' . implode(', ', $this->arguments) . ')';
}
}

View File

@@ -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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\SassColor;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A color literal.
*
* @internal
*/
final class ColorExpression implements Expression
{
/**
* @var SassColor
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -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\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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* 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 left as-is.
*
* @var string
* @readonly
*/
private $originalName;
/**
* The arguments to pass to the function.
*
* @var ArgumentInvocation
* @readonly
*/
private $arguments;
/**
* The namespace of the function being invoked, or `null` if it's invoked
* without a namespace.
*
* @var string|null
* @readonly
*/
private $namespace;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
/**
* @return string
*/
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 str_replace('_', '-', $this->originalName);
}
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;
}
}

View File

@@ -0,0 +1,89 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* 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()`.
*
* @var ArgumentInvocation
* @readonly
*/
private $arguments;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @var ArgumentDeclaration|null
*/
private static $declaration;
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;
}
}

View File

@@ -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\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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* 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.
*
* @var Interpolation
* @readonly
*/
private $name;
/**
* The arguments to pass to the function.
*
* @var ArgumentInvocation
* @readonly
*/
private $arguments;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,142 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\ListSeparator;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A list literal.
*
* @internal
*/
final class ListExpression implements Expression
{
/**
* @var Expression[]
* @readonly
*/
private $contents;
/**
* @var ListSeparator::*
* @readonly
*/
private $separator;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @var bool
* @readonly
*/
private $brackets;
/**
* ListExpression constructor.
*
* @param Expression[] $contents
* @param ListSeparator::* $separator
*/
public function __construct(array $contents, string $separator, FileSpan $span, bool $brackets = false)
{
$this->contents = $contents;
$this->separator = $separator;
$this->span = $span;
$this->brackets = $brackets;
}
/**
* @return Expression[]
*/
public function getContents(): array
{
return $this->contents;
}
/**
* @return ListSeparator::*
*/
public function getSeparator(): string
{
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 .= '[';
}
$buffer .= implode($this->separator === ListSeparator::COMMA ? ', ' : ' ', array_map(function ($element) {
return $this->elementNeedsParens($element) ? "($element)" : (string) $element;
}, $this->contents));
if ($this->hasBrackets()) {
$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;
}
}

View File

@@ -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\Sass\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A map literal.
*
* @internal
*/
final class MapExpression implements Expression
{
/**
* @var list<array{Expression, Expression}>
* @readonly
*/
private $pairs;
/**
* @var FileSpan
* @readonly
*/
private $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(function ($pair) {
return $pair[0] . ': ' . $pair[1];
}, $this->pairs)) . ')';
}
}

View File

@@ -0,0 +1,51 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A null literal.
*
* @internal
*/
final class NullExpression implements Expression
{
/**
* @var FileSpan
* @readonly
*/
private $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';
}
}

View File

@@ -0,0 +1,76 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\SassNumber;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A number literal.
*
* @internal
*/
final class NumberExpression implements Expression
{
/**
* @var float
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @var string|null
* @readonly
*/
private $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);
}
}

View File

@@ -0,0 +1,63 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* An expression wrapped in parentheses.
*
* @internal
*/
final class ParenthesizedExpression implements Expression
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 . ')';
}
}

View File

@@ -0,0 +1,51 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A parent selector reference, `&`.
*
* @internal
*/
final class SelectorExpression implements Expression
{
/**
* @var FileSpan
* @readonly
*/
private $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 '&';
}
}

View File

@@ -0,0 +1,182 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\Character;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A string literal.
*
* @internal
*/
final class StringExpression implements Expression
{
/**
* @var Interpolation
* @readonly
*/
private $text;
/**
* @var bool
* @readonly
*/
private $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;
}
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
*
* @return string
*/
private static function bestQuote(array $parts): string
{
$containsDoubleQuote = false;
foreach ($parts as $part) {
if (!\is_string($part)) {
continue;
}
if (false !== strpos($part, "'")) {
return '"';
}
if (false !== strpos($part, '"')) {
$containsDoubleQuote = true;
}
}
return $containsDoubleQuote ? "'": '"';
}
public function __toString(): string
{
return (string) $this->asInterpolation();
}
}

View File

@@ -0,0 +1,60 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* 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
{
/**
* @var SupportsCondition
* @readonly
*/
private $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;
}
}

View File

@@ -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\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A unary operator, as in `+$var` or `not fn()`.
*
* @internal
*/
final class UnaryOperationExpression implements Expression
{
/**
* @var UnaryOperator::*
* @readonly
*/
private $operator;
/**
* @var Expression
* @readonly
*/
private $operand;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param UnaryOperator::* $operator
*/
public function __construct(string $operator, Expression $operand, FileSpan $span)
{
$this->operator = $operator;
$this->operand = $operand;
$this->span = $span;
}
/**
* @return UnaryOperator::*
*/
public function getOperator()
{
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;
if ($this->operator === UnaryOperator::NOT) {
$buffer .= ' ';
}
$buffer .= $this->operand;
return $buffer;
}
}

View File

@@ -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\Expression;
/**
* @internal
*/
final class UnaryOperator
{
const PLUS = '+';
const MINUS = '-';
const DIVIDE = '/';
const NOT = 'not';
}

View File

@@ -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\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Value\Value;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* 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
{
/**
* @var Value
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,104 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\ExpressionVisitor;
/**
* A Sass variable.
*
* @internal
*/
final class VariableExpression implements Expression, SassReference
{
/**
* The name of this variable, with underscores converted to hyphens.
*
* @var string
* @readonly
*/
private $name;
/**
* The namespace of the variable being referenced, or `null` if it's
* referenced without a namespace.
*
* @var string|null
* @readonly
*/
private $namespace;
/**
* @var FileSpan
* @readonly
*/
private $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
{
if ($this->namespace === null) {
return '$' . $this->name;
}
return $this->namespace . '$' . $this->name;
}
}

View File

@@ -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
{
}

View File

@@ -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 ScssPhp\ScssPhp\Ast\Sass\Expression\StringExpression;
use ScssPhp\ScssPhp\Ast\Sass\Import;
use ScssPhp\ScssPhp\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.
*
* @var string
* @readonly
*/
private $urlString;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(string $urlString, FileSpan $span)
{
$this->urlString = $urlString;
$this->span = $span;
}
public function getUrlString(): string
{
return $this->urlString;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function __toString(): string
{
return StringExpression::quoteText($this->urlString);
}
}

View File

@@ -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\Import;
use ScssPhp\ScssPhp\Ast\Sass\Import;
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
use ScssPhp\ScssPhp\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.
*
* @var Interpolation
* @readonly
*/
private $url;
/**
* The modifiers (such as media or supports queries) attached to this import,
* or `null` if none are attached.
*
* @var Interpolation|null
* @readonly
*/
private $modifiers;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,138 @@
<?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 ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* Plain text interpolated with Sass expressions.
*
* @internal
*/
final class Interpolation implements SassNode
{
/**
* @var list<string|Expression>
* @readonly
*/
private $contents;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* Creates a new {@see Interpolation} by concatenating a sequence of strings,
* {@see Expression}s, or nested {@see Interpolation}s.
*
* @param array<string|Expression|Interpolation> $contents
*/
public static function concat(array $contents, FileSpan $span): Interpolation
{
$buffer = new InterpolationBuffer();
foreach ($contents as $element) {
if (\is_string($element)) {
$buffer->write($element);
} elseif ($element instanceof Expression) {
$buffer->add($element);
} elseif ($element instanceof Interpolation) {
$buffer->addInterpolation($element);
} else {
throw new \InvalidArgumentException(sprintf('The elements in $contents may only contains strings, Expressions, or Interpolations, "%s" given.', \is_object($element) ? get_class($element) : gettype($element)));
}
}
return $buffer->buildInterpolation($span);
}
/**
* @param list<string|Expression> $contents
*/
public function __construct(array $contents, FileSpan $span)
{
for ($i = 0; $i < \count($contents); $i++) {
if (!\is_string($contents[$i]) && !$contents[$i] instanceof Expression) {
throw new \TypeError('The contents of an Interpolation may only contain strings or Expression instances.');
}
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;
}
/**
* 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(function ($value) {
return \is_string($value) ? $value : '#{' . $value .'}';
}, $this->contents));
}
}

View File

@@ -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 ScssPhp\ScssPhp\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;
}

View File

@@ -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
{
}

View File

@@ -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 ScssPhp\ScssPhp\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;
}

View File

@@ -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);
}

View File

@@ -0,0 +1,80 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@at-root` rule.
*
* This moves it contents "up" the tree through parent nodes.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class AtRootRule extends ParentStatement
{
/**
* @var Interpolation|null
* @readonly
*/
private $query;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -0,0 +1,93 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* An unknown at-rule.
*
* @extends ParentStatement<Statement[]|null>
*
* @internal
*/
final class AtRule extends ParentStatement
{
/**
* @var Interpolation
* @readonly
*/
private $name;
/**
* @var Interpolation|null
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $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) . '}';
}
}

View File

@@ -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\Statement;
use ScssPhp\ScssPhp\Ast\Sass\ArgumentDeclaration;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\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
{
/**
* @var string
* @readonly
*/
private $name;
/**
* @var ArgumentDeclaration
* @readonly
*/
private $arguments;
/**
* @var SilentComment|null
* @readonly
*/
private $comment;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param Statement[] $children
*/
public function __construct(string $name, ArgumentDeclaration $arguments, FileSpan $span, array $children, ?SilentComment $comment = null)
{
$this->name = $name;
$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;
}
final public function getArguments(): ArgumentDeclaration
{
return $this->arguments;
}
/**
* @return SilentComment|null
*/
final public function getComment(): ?SilentComment
{
return $this->comment;
}
final public function getSpan(): FileSpan
{
return $this->span;
}
}

View File

@@ -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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* 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()) . '}';
}
}

View File

@@ -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\Sass\Statement;
use ScssPhp\ScssPhp\Ast\Sass\ArgumentInvocation;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* 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.
*
* @var ArgumentInvocation
* @readonly
*/
private $arguments;
/**
* @var FileSpan
* @readonly
*/
private $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);";
}
}

View File

@@ -0,0 +1,66 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@debug` rule.
*
* This prints a Sass value for debugging purposes.
*
* @internal
*/
final class DebugRule implements Statement
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 . ';';
}
}

View File

@@ -0,0 +1,143 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A declaration (that is, a `name: value` pair).
*
* @extends ParentStatement<Statement[]|null>
*
* @internal
*/
final class Declaration extends ParentStatement
{
/**
* @var Interpolation
* @readonly
*/
private $name;
/**
* The value of this declaration.
*
* If {@see getChildren} is `null`, this is never `null`. Otherwise, it may or may
* not be `null`.
*
* @var Expression|null
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $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
{
$declaration = new self($name, $value, $span);
if ($declaration->isCustomProperty() && !$value instanceof StringExpression) {
throw new \InvalidArgumentException(sprintf('Declarations whose names begin with "--" must have StringExpression values (got %s)', get_class($value)));
}
return $declaration;
}
/**
* @param Statement[] $children
*/
public static function nested(Interpolation $name, array $children, FileSpan $span, ?Expression $value = null): self
{
$declaration = new self($name, $value, $span, $children);
if ($declaration->isCustomProperty() && !$value instanceof StringExpression) {
throw new \InvalidArgumentException('Declarations whose names begin with "--" may not be nested.');
}
return $declaration;
}
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 0 === strpos($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) . '}';
}
}

View File

@@ -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\Sass\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* An `@each` rule.
*
* This iterates over values in a list or map.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class EachRule extends ParentStatement
{
/**
* @var string[]
* @readonly
*/
private $variables;
/**
* @var Expression
* @readonly
*/
private $list;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param 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 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(function ($variable) { return '$' . $variable; }, $this->variables)) . ' in ' . $this->list . ' {' . implode(' ', $this->getChildren()) . '}';
}
}

View File

@@ -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()) . '}';
}
}

View File

@@ -0,0 +1,66 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@error` rule.
*
* This emits an error and stops execution.
*
* @internal
*/
final class ErrorRule implements Statement
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 . ';';
}
}

View File

@@ -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\Sass\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* An `@extend` rule.
*
* This gives one selector all the styling of another.
*
* @internal
*/
final class ExtendRule implements Statement
{
/**
* @var Interpolation
* @readonly
*/
private $selector;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @var bool
* @readonly
*/
private $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' : '') . ';';
}
}

View File

@@ -0,0 +1,111 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@for` rule.
*
* This iterates a set number of times.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class ForRule extends ParentStatement
{
/**
* @var string
* @readonly
*/
private $variable;
/**
* @var Expression
* @readonly
*/
private $from;
/**
* @var Expression
* @readonly
*/
private $to;
/**
* @var bool
* @readonly
*/
private $exclusive;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* 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()) . '}';
}
}

View File

@@ -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;
}
}

View File

@@ -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\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
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @param Statement[] $children
*/
public function __construct(Expression $expression, array $children)
{
$this->expression = $expression;
parent::__construct($children);
}
public function getExpression(): Expression
{
return $this->expression;
}
}

View File

@@ -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 ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* An `@if` rule.
*
* This conditionally executes a block of code.
*
* @internal
*/
final class IfRule implements Statement
{
/**
* @var IfClause[]
* @readonly
*/
private $clauses;
/**
* @var ElseClause|null
* @readonly
*/
private $lastClause;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param 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 IfClause[]
*/
public function getClauses(): array
{
return $this->clauses;
}
/**
* The final, unconditional `@else` clause.
*
* This is `null` if there is no unconditional `@else`.
*
* @return ElseClause|null
*/
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);
}
}

View File

@@ -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\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Import\DynamicImport;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
/**
* The superclass of `@if` and `@else` clauses.
*
* @internal
*/
abstract class IfRuleClause
{
/**
* @var Statement[]
* @readonly
*/
private $children;
/**
* @var bool
* @readonly
*/
private $declarations = false;
/**
* @param Statement[] $children
*/
public function __construct(array $children)
{
$this->children = $children;
foreach ($children as $child) {
if ($child instanceof VariableDeclaration || $child instanceof FunctionRule || $child instanceof MixinRule) {
$this->declarations = true;
break;
}
if ($child instanceof ImportRule) {
foreach ($child->getImports() as $import) {
if ($import instanceof DynamicImport) {
$this->declarations = true;
break 2;
}
}
}
}
}
/**
* @return Statement[]
*/
final public function getChildren(): array
{
return $this->children;
}
final public function hasDeclarations(): bool
{
return $this->declarations;
}
}

View File

@@ -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\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Import;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* An `@import` rule.
*
* @internal
*/
final class ImportRule implements Statement
{
/**
* @var Import[]
* @readonly
*/
private $imports;
/**
* @var FileSpan
* @readonly
*/
private $span;
/**
* @param Import[] $imports
*/
public function __construct(array $imports, FileSpan $span)
{
$this->imports = $imports;
$this->span = $span;
}
/**
* @return 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) . ';';
}
}

View File

@@ -0,0 +1,149 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A mixin invocation.
*
* @internal
*/
final class IncludeRule implements Statement, CallableInvocation, SassReference
{
/**
* @var string|null
* @readonly
*/
private $namespace;
/**
* @var string
* @readonly
*/
private $name;
/**
* @var ArgumentInvocation
* @readonly
*/
private $arguments;
/**
* @var ContentBlock|null
* @readonly
*/
private $content;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(string $name, ArgumentInvocation $arguments, FileSpan $span, ?string $namespace = null,?ContentBlock $content = null)
{
$this->name = $name;
$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;
}
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;
}
}

View File

@@ -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\Sass\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A loud CSS-style comment.
*
* @internal
*/
final class LoudComment implements Statement
{
/**
* @var Interpolation
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,75 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@media` rule.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class MediaRule extends ParentStatement
{
/**
* @var Interpolation
* @readonly
*/
private $query;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -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\ArgumentDeclaration;
use ScssPhp\ScssPhp\Ast\Sass\SassDeclaration;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* 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.
*
* @var bool|null
*/
private $content;
/**
* @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;
}
}

View File

@@ -0,0 +1,86 @@
<?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
* @readonly
*/
private $children;
/**
* @var bool
* @readonly
*/
private $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()
{
return $this->children;
}
final public function hasDeclarations(): bool
{
return $this->declarations;
}
}

View File

@@ -0,0 +1,66 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@return` rule.
*
* This exits from the current function body with a return value.
*
* @internal
*/
final class ReturnRule implements Statement
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 . ';';
}
}

View File

@@ -0,0 +1,63 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A silent Sass-style comment.
*
* @internal
*/
final class SilentComment implements Statement
{
/**
* @var string
* @readonly
*/
private $text;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,77 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A style rule.
*
* This applies style declarations to elements that match a given selector.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class StyleRule extends ParentStatement
{
/**
* @var Interpolation
* @readonly
*/
private $selector;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -0,0 +1,126 @@
<?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\Exception\SassFormatException;
use ScssPhp\ScssPhp\Logger\LoggerInterface;
use ScssPhp\ScssPhp\Parser\CssParser;
use ScssPhp\ScssPhp\Parser\ScssParser;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\SourceSpan\SourceFile;
use ScssPhp\ScssPhp\Syntax;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A Sass stylesheet.
*
* This is the root Sass node. It contains top-level statements.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class Stylesheet extends ParentStatement
{
/**
* @var bool
* @readonly
*/
private $plainCss;
/**
* @var FileSpan
* @readonly
*/
private $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);
}
/**
* @param Syntax::* $syntax
*
* @throws SassFormatException when parsing fails
*/
public static function parse(string $contents, string $syntax, ?LoggerInterface $logger = null, ?string $sourceUrl = null): self
{
switch ($syntax) {
case Syntax::SASS:
return self::parseSass($contents, $logger, $sourceUrl);
case Syntax::SCSS:
return self::parseScss($contents, $logger, $sourceUrl);
case Syntax::CSS:
return self::parseCss($contents, $logger, $sourceUrl);
default:
throw new \InvalidArgumentException("Unknown syntax $syntax.");
}
}
/**
* @throws SassFormatException when parsing fails
*/
public static function parseSass(string $contents, ?LoggerInterface $logger = null, ?string $sourceUrl = null): self
{
$file = new SourceFile($contents, $sourceUrl);
$span = $file->span(0, 0);
throw new SassFormatException('The Sass indented syntax is not implemented.', $span);
}
/**
* @throws SassFormatException when parsing fails
*/
public static function parseScss(string $contents, ?LoggerInterface $logger = null, ?string $sourceUrl = null): self
{
return (new ScssParser($contents, $logger, $sourceUrl))->parse();
}
/**
* @throws SassFormatException when parsing fails
*/
public static function parseCss(string $contents, ?LoggerInterface $logger = null, ?string $sourceUrl = null): self
{
return (new CssParser($contents, $logger, $sourceUrl))->parse();
}
public function __toString(): string
{
return implode(' ', $this->getChildren());
}
}

View File

@@ -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\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@supports` rule.
*
* @extends ParentStatement<Statement[]>
*
* @internal
*/
final class SupportsRule extends ParentStatement
{
/**
* @var SupportsCondition
* @readonly
*/
private $condition;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -0,0 +1,174 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util;
use ScssPhp\ScssPhp\Util\SpanUtil;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A variable declaration.
*
* This defines or sets a variable.
*
* @internal
*/
final class VariableDeclaration implements Statement, SassDeclaration
{
/**
* @var string|null
* @readonly
*/
private $namespace;
/**
* @var string
* @readonly
*/
private $name;
/**
* @var SilentComment|null
* @readonly
*/
private $comment;
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var bool
* @readonly
*/
private $guarded;
/**
* @var bool
* @readonly
*/
private $global;
/**
* @var FileSpan
* @readonly
*/
private $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;
}
}

View File

@@ -0,0 +1,66 @@
<?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\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* A `@warn` rule.
*
* This prints a Sass value—usually a string—to warn the user of something.
*
* @internal
*/
final class WarnRule implements Statement
{
/**
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 . ';';
}
}

View File

@@ -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\Statement;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Statement;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Visitor\StatementVisitor;
/**
* 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
{
/**
* @var Expression
* @readonly
*/
private $condition;
/**
* @var FileSpan
* @readonly
*/
private $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()) . '}';
}
}

View File

@@ -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 defining the condition a `@supports` rule selects.
*
* @internal
*/
interface SupportsCondition extends SassNode
{
}

View File

@@ -0,0 +1,61 @@
<?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\SupportsCondition;
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* A supports condition that represents the forwards-compatible
* `<general-enclosed>` production.
*
* @internal
*/
final class SupportsAnything implements SupportsCondition
{
/**
* The contents of the condition.
*
* @var Interpolation
* @readonly
*/
private $contents;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(Interpolation $contents, FileSpan $span)
{
$this->contents = $contents;
$this->span = $span;
}
public function getContents(): Interpolation
{
return $this->contents;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function __toString(): string
{
return "($this->contents)";
}
}

View File

@@ -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\SupportsCondition;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\Ast\Sass\Expression\StringExpression;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
use ScssPhp\ScssPhp\Util\StringUtil;
/**
* A condition that selects for browsers where a given declaration is
* supported.
*
* @internal
*/
final class SupportsDeclaration implements SupportsCondition
{
/**
* The name of the declaration being tested.
*
* @var Expression
* @readonly
*/
private $name;
/**
* The value of the declaration being tested.
*
* @var Expression
* @readonly
*/
private $value;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(Expression $name, Expression $value, FileSpan $span)
{
$this->name = $name;
$this->value = $value;
$this->span = $span;
}
public function getName(): Expression
{
return $this->name;
}
public function getValue(): Expression
{
return $this->value;
}
public function getSpan(): FileSpan
{
return $this->span;
}
/**
* 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 $this->name instanceof StringExpression && !$this->name->hasQuotes() && StringUtil::startsWith($this->name->getText()->getInitialPlain(), '--');
}
public function __toString(): string
{
return "($this->name: $this->value)";
}
}

View File

@@ -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\SupportsCondition;
use ScssPhp\ScssPhp\Ast\Sass\Interpolation;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* A function-syntax condition.
*
* @internal
*/
final class SupportsFunction implements SupportsCondition
{
/**
* The name of the function.
*
* @var Interpolation
* @readonly
*/
private $name;
/**
* The arguments of the function.
*
* @var Interpolation
* @readonly
*/
private $arguments;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(Interpolation $name, Interpolation $arguments, FileSpan $span)
{
$this->name = $name;
$this->arguments = $arguments;
$this->span = $span;
}
public function getName(): Interpolation
{
return $this->name;
}
public function getArguments(): Interpolation
{
return $this->arguments;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function __toString(): string
{
return "$this->name($this->arguments)";
}
}

View File

@@ -0,0 +1,60 @@
<?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\SupportsCondition;
use ScssPhp\ScssPhp\Ast\Sass\Expression;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* An interpolated condition.
*
* @internal
*/
final class SupportsInterpolation implements SupportsCondition
{
/**
* The expression in the interpolation.
*
* @var Expression
* @readonly
*/
private $expression;
/**
* @var FileSpan
* @readonly
*/
private $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 __toString(): string
{
return '#{' . $this->expression . '}';
}
}

View File

@@ -0,0 +1,63 @@
<?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\SupportsCondition;
use ScssPhp\ScssPhp\Ast\Sass\SupportsCondition;
use ScssPhp\ScssPhp\SourceSpan\FileSpan;
/**
* A negated condition.
*
* @internal
*/
final class SupportsNegation implements SupportsCondition
{
/**
* The condition that's been negated.
*
* @var SupportsCondition
* @readonly
*/
private $condition;
/**
* @var FileSpan
* @readonly
*/
private $span;
public function __construct(SupportsCondition $condition, FileSpan $span)
{
$this->condition = $condition;
$this->span = $span;
}
public function getCondition(): SupportsCondition
{
return $this->condition;
}
public function getSpan(): FileSpan
{
return $this->span;
}
public function __toString(): string
{
if ($this->condition instanceof SupportsNegation || $this->condition instanceof SupportsOperation) {
return "not ($this->condition)";
}
return 'not ' . $this->condition;
}
}

Some files were not shown because too many files have changed in this diff Show More