Skip to content

Commit

Permalink
Merge pull request #572 from stof/stringable_ast
Browse files Browse the repository at this point in the history
Make AST nodes castable to string
  • Loading branch information
stof committed May 12, 2022
2 parents 2e77caa + 7b5838d commit e4dbc63
Show file tree
Hide file tree
Showing 59 changed files with 524 additions and 7 deletions.
2 changes: 2 additions & 0 deletions src/Ast/AstNode.php
Expand Up @@ -22,4 +22,6 @@
interface AstNode
{
public function getSpan(): FileSpan;

public function __toString(): string;
}
9 changes: 9 additions & 0 deletions src/Ast/Css/CssValue.php
Expand Up @@ -59,4 +59,13 @@ public function getSpan(): FileSpan
{
return $this->span;
}

public function __toString(): string
{
if (\is_array($this->value)) {
return implode($this->value);
}

return (string) $this->value;
}
}
6 changes: 6 additions & 0 deletions src/Ast/Css/ModifiableCssNode.php
Expand Up @@ -12,6 +12,7 @@

namespace ScssPhp\ScssPhp\Ast\Css;

use ScssPhp\ScssPhp\Serializer\Serializer;
use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor;

/**
Expand Down Expand Up @@ -161,4 +162,9 @@ public function remove(): void
$this->parent = null;
$this->indexInParent = null;
}

public function __toString(): string
{
return Serializer::serialize($this, true)->getCss();
}
}
9 changes: 9 additions & 0 deletions src/Ast/Sass/Argument.php
Expand Up @@ -70,4 +70,13 @@ public function getSpan(): FileSpan
{
return $this->span;
}

public function __toString(): string
{
if ($this->defaultValue === null) {
return $this->name;
}

return $this->name . ': ' . $this->defaultValue;
}
}
13 changes: 13 additions & 0 deletions src/Ast/Sass/ArgumentDeclaration.php
Expand Up @@ -78,4 +78,17 @@ public function getSpan(): FileSpan
{
return $this->span;
}

public function __toString(): string
{
$parts = [];
foreach ($this->arguments as $arg) {
$parts[] = "\$$arg";
}
if ($this->restArgument !== null) {
$parts[] = "\$$this->restArgument...";
}

return implode(', ', $parts);
}
}
16 changes: 16 additions & 0 deletions src/Ast/Sass/ArgumentInvocation.php
Expand Up @@ -108,4 +108,20 @@ 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) . ')';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/ConfiguredVariable.php
Expand Up @@ -78,4 +78,9 @@ public function getNameSpan(): FileSpan
{
return SpanUtil::initialIdentifier($this->span, 1);
}

public function __toString(): string
{
return '$' . $this->name . ': ' . $this->expression . ($this->guarded ? ' !default' : '');
}
}
29 changes: 29 additions & 0 deletions src/Ast/Sass/Expression/BinaryOperationExpression.php
Expand Up @@ -116,4 +116,33 @@ 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;
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/BooleanExpression.php
Expand Up @@ -55,4 +55,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitBooleanExpression($this);
}

public function __toString(): string
{
return $this->value ? 'true' : 'false';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/CalculationExpression.php
Expand Up @@ -227,4 +227,9 @@ private static function verify(Expression $expression): void

throw new \InvalidArgumentException('Invalid calculation argument.');
}

public function __toString(): string
{
return $this->name . '(' . implode(', ', $this->arguments) . ')';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/ColorExpression.php
Expand Up @@ -56,4 +56,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitColorExpression($this);
}

public function __toString(): string
{
return (string) $this->value;
}
}
13 changes: 13 additions & 0 deletions src/Ast/Sass/Expression/FunctionExpression.php
Expand Up @@ -125,4 +125,17 @@ 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;
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/IfExpression.php
Expand Up @@ -63,4 +63,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitIfExpression($this);
}

public function __toString(): string
{
return 'if' . $this->arguments;
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/InterpolatedFunctionExpression.php
Expand Up @@ -76,4 +76,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitInterpolatedFunctionExpression($this);
}

public function __toString(): string
{
return $this->name . $this->arguments;
}
}
47 changes: 47 additions & 0 deletions src/Ast/Sass/Expression/ListExpression.php
Expand Up @@ -92,4 +92,51 @@ 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;
}
}
7 changes: 7 additions & 0 deletions src/Ast/Sass/Expression/MapExpression.php
Expand Up @@ -61,4 +61,11 @@ 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)) . ')';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/NullExpression.php
Expand Up @@ -43,4 +43,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitNullExpression($this);
}

public function __toString(): string
{
return 'null';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/NumberExpression.php
Expand Up @@ -73,4 +73,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitNumberExpression($this);
}

public function __toString(): string
{
return $this->value . ($this->unit ?? '');
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/ParenthesizedExpression.php
Expand Up @@ -55,4 +55,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitParenthesizedExpression($this);
}

public function __toString(): string
{
return '(' . $this->expression . ')';
}
}
5 changes: 5 additions & 0 deletions src/Ast/Sass/Expression/SelectorExpression.php
Expand Up @@ -43,4 +43,9 @@ public function accept(ExpressionVisitor $visitor)
{
return $visitor->visitSelectorExpression($this);
}

public function __toString(): string
{
return '&';
}
}
35 changes: 28 additions & 7 deletions src/Ast/Sass/Expression/StringExpression.php
Expand Up @@ -52,6 +52,20 @@ public static function plain(string $text, FileSpan $span, bool $quotes = false)
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;
Expand Down Expand Up @@ -87,7 +101,7 @@ public function asInterpolation(bool $static = false, string $quote = null): Int
if ($value instanceof Expression) {
$buffer->add($value);
} else {
self::quoteInnerText($value, $quote, $buffer, $static);
$buffer->write(self::quoteInnerText($value, $quote, $static));
}
}

Expand All @@ -96,41 +110,43 @@ public function asInterpolation(bool $static = false, string $quote = null): Int
return $buffer->buildInterpolation($this->text->getSpan());
}

private static function quoteInnerText(string $value, string $quote, InterpolationBuffer $buffer, bool $static = false): void
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->write('\\a');
$buffer .= '\\a';

if ($i !== $length - 1) {
$next = $value[$i + 1];

if (Character::isWhitespace($next) || Character::isHex($next)) {
$buffer->write(' ');
$buffer .= ' ';
}
}
} else {
if ($char === $quote || $char === '\\' || ($static && $char === '#' && $i < $length - 1 && $value[$i + 1] === '{')) {
$buffer->write('\\');
$buffer .= '\\';
}

if (\ord($char) < 0x80) {
$buffer->write($char);
$buffer .= $char;
} else {
if (!preg_match('/./usA', $value, $m, 0, $i)) {
throw new \UnexpectedValueException('Invalid UTF-8 char');
}

$buffer->write($m[0]);
$buffer .= $m[0];
$i += \strlen($m[0]) - 1; // skip over the extra bytes that have been processed.
}
}
}

return $buffer;
}

/**
Expand Down Expand Up @@ -158,4 +174,9 @@ private static function bestQuote(array $parts): string

return $containsDoubleQuote ? "'": '"';
}

public function __toString(): string
{
return (string) $this->asInterpolation();
}
}

0 comments on commit e4dbc63

Please sign in to comment.