diff --git a/src/Ast/AstNode.php b/src/Ast/AstNode.php index 84f6ae9b..ec15547c 100644 --- a/src/Ast/AstNode.php +++ b/src/Ast/AstNode.php @@ -22,4 +22,6 @@ interface AstNode { public function getSpan(): FileSpan; + + public function __toString(): string; } diff --git a/src/Ast/Css/CssValue.php b/src/Ast/Css/CssValue.php index 7b25e05f..76e93bf0 100644 --- a/src/Ast/Css/CssValue.php +++ b/src/Ast/Css/CssValue.php @@ -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; + } } diff --git a/src/Ast/Css/ModifiableCssNode.php b/src/Ast/Css/ModifiableCssNode.php index bcd9b10b..5441b792 100644 --- a/src/Ast/Css/ModifiableCssNode.php +++ b/src/Ast/Css/ModifiableCssNode.php @@ -12,6 +12,7 @@ namespace ScssPhp\ScssPhp\Ast\Css; +use ScssPhp\ScssPhp\Serializer\Serializer; use ScssPhp\ScssPhp\Visitor\ModifiableCssVisitor; /** @@ -161,4 +162,9 @@ public function remove(): void $this->parent = null; $this->indexInParent = null; } + + public function __toString(): string + { + return Serializer::serialize($this, true)->getCss(); + } } diff --git a/src/Ast/Sass/Argument.php b/src/Ast/Sass/Argument.php index 60b8fdc4..80d4006f 100644 --- a/src/Ast/Sass/Argument.php +++ b/src/Ast/Sass/Argument.php @@ -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; + } } diff --git a/src/Ast/Sass/ArgumentDeclaration.php b/src/Ast/Sass/ArgumentDeclaration.php index 9f70daba..b810f495 100644 --- a/src/Ast/Sass/ArgumentDeclaration.php +++ b/src/Ast/Sass/ArgumentDeclaration.php @@ -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); + } } diff --git a/src/Ast/Sass/ArgumentInvocation.php b/src/Ast/Sass/ArgumentInvocation.php index 124e47ca..3be35172 100644 --- a/src/Ast/Sass/ArgumentInvocation.php +++ b/src/Ast/Sass/ArgumentInvocation.php @@ -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) . ')'; + } } diff --git a/src/Ast/Sass/ConfiguredVariable.php b/src/Ast/Sass/ConfiguredVariable.php index df747eb6..3d82bd39 100644 --- a/src/Ast/Sass/ConfiguredVariable.php +++ b/src/Ast/Sass/ConfiguredVariable.php @@ -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' : ''); + } } diff --git a/src/Ast/Sass/Expression/BinaryOperationExpression.php b/src/Ast/Sass/Expression/BinaryOperationExpression.php index c40806e6..e00b7cfb 100644 --- a/src/Ast/Sass/Expression/BinaryOperationExpression.php +++ b/src/Ast/Sass/Expression/BinaryOperationExpression.php @@ -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; + } } diff --git a/src/Ast/Sass/Expression/BooleanExpression.php b/src/Ast/Sass/Expression/BooleanExpression.php index f0cedcd2..0981d3eb 100644 --- a/src/Ast/Sass/Expression/BooleanExpression.php +++ b/src/Ast/Sass/Expression/BooleanExpression.php @@ -55,4 +55,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitBooleanExpression($this); } + + public function __toString(): string + { + return $this->value ? 'true' : 'false'; + } } diff --git a/src/Ast/Sass/Expression/CalculationExpression.php b/src/Ast/Sass/Expression/CalculationExpression.php index 2f7cfa3b..7f5e611f 100644 --- a/src/Ast/Sass/Expression/CalculationExpression.php +++ b/src/Ast/Sass/Expression/CalculationExpression.php @@ -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) . ')'; + } } diff --git a/src/Ast/Sass/Expression/ColorExpression.php b/src/Ast/Sass/Expression/ColorExpression.php index 62efa4ac..dc8583ee 100644 --- a/src/Ast/Sass/Expression/ColorExpression.php +++ b/src/Ast/Sass/Expression/ColorExpression.php @@ -56,4 +56,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitColorExpression($this); } + + public function __toString(): string + { + return (string) $this->value; + } } diff --git a/src/Ast/Sass/Expression/FunctionExpression.php b/src/Ast/Sass/Expression/FunctionExpression.php index 3705db6d..c0a642af 100644 --- a/src/Ast/Sass/Expression/FunctionExpression.php +++ b/src/Ast/Sass/Expression/FunctionExpression.php @@ -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; + } } diff --git a/src/Ast/Sass/Expression/IfExpression.php b/src/Ast/Sass/Expression/IfExpression.php index 4dc86263..24db7eb7 100644 --- a/src/Ast/Sass/Expression/IfExpression.php +++ b/src/Ast/Sass/Expression/IfExpression.php @@ -63,4 +63,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitIfExpression($this); } + + public function __toString(): string + { + return 'if' . $this->arguments; + } } diff --git a/src/Ast/Sass/Expression/InterpolatedFunctionExpression.php b/src/Ast/Sass/Expression/InterpolatedFunctionExpression.php index ad022fa5..20876581 100644 --- a/src/Ast/Sass/Expression/InterpolatedFunctionExpression.php +++ b/src/Ast/Sass/Expression/InterpolatedFunctionExpression.php @@ -76,4 +76,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitInterpolatedFunctionExpression($this); } + + public function __toString(): string + { + return $this->name . $this->arguments; + } } diff --git a/src/Ast/Sass/Expression/ListExpression.php b/src/Ast/Sass/Expression/ListExpression.php index 0766a3fd..053a212e 100644 --- a/src/Ast/Sass/Expression/ListExpression.php +++ b/src/Ast/Sass/Expression/ListExpression.php @@ -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; + } } diff --git a/src/Ast/Sass/Expression/MapExpression.php b/src/Ast/Sass/Expression/MapExpression.php index 5f12f870..6615f2a7 100644 --- a/src/Ast/Sass/Expression/MapExpression.php +++ b/src/Ast/Sass/Expression/MapExpression.php @@ -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)) . ')'; + } } diff --git a/src/Ast/Sass/Expression/NullExpression.php b/src/Ast/Sass/Expression/NullExpression.php index 9697cfa3..6db94f16 100644 --- a/src/Ast/Sass/Expression/NullExpression.php +++ b/src/Ast/Sass/Expression/NullExpression.php @@ -43,4 +43,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitNullExpression($this); } + + public function __toString(): string + { + return 'null'; + } } diff --git a/src/Ast/Sass/Expression/NumberExpression.php b/src/Ast/Sass/Expression/NumberExpression.php index fb468512..9a11cfc7 100644 --- a/src/Ast/Sass/Expression/NumberExpression.php +++ b/src/Ast/Sass/Expression/NumberExpression.php @@ -73,4 +73,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitNumberExpression($this); } + + public function __toString(): string + { + return $this->value . ($this->unit ?? ''); + } } diff --git a/src/Ast/Sass/Expression/ParenthesizedExpression.php b/src/Ast/Sass/Expression/ParenthesizedExpression.php index dccfd4a1..6c2e7ade 100644 --- a/src/Ast/Sass/Expression/ParenthesizedExpression.php +++ b/src/Ast/Sass/Expression/ParenthesizedExpression.php @@ -55,4 +55,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitParenthesizedExpression($this); } + + public function __toString(): string + { + return '(' . $this->expression . ')'; + } } diff --git a/src/Ast/Sass/Expression/SelectorExpression.php b/src/Ast/Sass/Expression/SelectorExpression.php index df20e79d..18ffe356 100644 --- a/src/Ast/Sass/Expression/SelectorExpression.php +++ b/src/Ast/Sass/Expression/SelectorExpression.php @@ -43,4 +43,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitSelectorExpression($this); } + + public function __toString(): string + { + return '&'; + } } diff --git a/src/Ast/Sass/Expression/StringExpression.php b/src/Ast/Sass/Expression/StringExpression.php index 5c1bb59b..5b071f72 100644 --- a/src/Ast/Sass/Expression/StringExpression.php +++ b/src/Ast/Sass/Expression/StringExpression.php @@ -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; @@ -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)); } } @@ -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; } /** @@ -158,4 +174,9 @@ private static function bestQuote(array $parts): string return $containsDoubleQuote ? "'": '"'; } + + public function __toString(): string + { + return (string) $this->asInterpolation(); + } } diff --git a/src/Ast/Sass/Expression/UnaryOperationExpression.php b/src/Ast/Sass/Expression/UnaryOperationExpression.php index 0890102c..ecb89043 100644 --- a/src/Ast/Sass/Expression/UnaryOperationExpression.php +++ b/src/Ast/Sass/Expression/UnaryOperationExpression.php @@ -73,4 +73,15 @@ 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; + } } diff --git a/src/Ast/Sass/Expression/ValueExpression.php b/src/Ast/Sass/Expression/ValueExpression.php index 384bb89b..d2d6549f 100644 --- a/src/Ast/Sass/Expression/ValueExpression.php +++ b/src/Ast/Sass/Expression/ValueExpression.php @@ -59,4 +59,9 @@ public function accept(ExpressionVisitor $visitor) { return $visitor->visitValueExpression($this); } + + public function __toString(): string + { + return (string) $this->value; + } } diff --git a/src/Ast/Sass/Expression/VariableExpression.php b/src/Ast/Sass/Expression/VariableExpression.php index 8424e459..5ccb4498 100644 --- a/src/Ast/Sass/Expression/VariableExpression.php +++ b/src/Ast/Sass/Expression/VariableExpression.php @@ -92,4 +92,13 @@ 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; + } } diff --git a/src/Ast/Sass/Import/DynamicImport.php b/src/Ast/Sass/Import/DynamicImport.php index 5d53e350..ffa70b69 100644 --- a/src/Ast/Sass/Import/DynamicImport.php +++ b/src/Ast/Sass/Import/DynamicImport.php @@ -12,6 +12,7 @@ namespace ScssPhp\ScssPhp\Ast\Sass\Import; +use ScssPhp\ScssPhp\Ast\Sass\Expression\StringExpression; use ScssPhp\ScssPhp\Ast\Sass\Import; use ScssPhp\ScssPhp\SourceSpan\FileSpan; @@ -53,4 +54,9 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + return StringExpression::quoteText($this->urlString); + } } diff --git a/src/Ast/Sass/Import/StaticImport.php b/src/Ast/Sass/Import/StaticImport.php index c36048dd..e63c65ba 100644 --- a/src/Ast/Sass/Import/StaticImport.php +++ b/src/Ast/Sass/Import/StaticImport.php @@ -79,4 +79,19 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + $buffer = (string) $this->url; + + if ($this->supports !== null) { + $buffer .= " supports($this->supports)"; + } + + if ($this->media !== null) { + $buffer .= ' ' . $this->media; + } + + return $buffer; + } } diff --git a/src/Ast/Sass/Interpolation.php b/src/Ast/Sass/Interpolation.php index 38d8af91..bf2b55f0 100644 --- a/src/Ast/Sass/Interpolation.php +++ b/src/Ast/Sass/Interpolation.php @@ -102,4 +102,11 @@ public function getInitialPlain(): string return ''; } + + public function __toString(): string + { + return implode('', array_map(function ($value) { + return \is_string($value) ? $value : '#{' . $value .'}'; + }, $this->contents)); + } } diff --git a/src/Ast/Sass/Statement/AtRootRule.php b/src/Ast/Sass/Statement/AtRootRule.php index 09f616be..3da58c3d 100644 --- a/src/Ast/Sass/Statement/AtRootRule.php +++ b/src/Ast/Sass/Statement/AtRootRule.php @@ -67,4 +67,14 @@ 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()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/AtRule.php b/src/Ast/Sass/Statement/AtRule.php index 12d70086..d401284d 100644 --- a/src/Ast/Sass/Statement/AtRule.php +++ b/src/Ast/Sass/Statement/AtRule.php @@ -74,4 +74,20 @@ 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) . '}'; + } } diff --git a/src/Ast/Sass/Statement/ContentBlock.php b/src/Ast/Sass/Statement/ContentBlock.php index 7c65b036..02c2e699 100644 --- a/src/Ast/Sass/Statement/ContentBlock.php +++ b/src/Ast/Sass/Statement/ContentBlock.php @@ -36,4 +36,11 @@ 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()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/ContentRule.php b/src/Ast/Sass/Statement/ContentRule.php index ed79a4cc..ee952263 100644 --- a/src/Ast/Sass/Statement/ContentRule.php +++ b/src/Ast/Sass/Statement/ContentRule.php @@ -63,4 +63,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitContentRule($this); } + + public function __toString(): string + { + return $this->arguments->isEmpty() ? '@content;' : "@content($this->arguments);"; + } } diff --git a/src/Ast/Sass/Statement/DebugRule.php b/src/Ast/Sass/Statement/DebugRule.php index 142aab68..57490e8d 100644 --- a/src/Ast/Sass/Statement/DebugRule.php +++ b/src/Ast/Sass/Statement/DebugRule.php @@ -58,4 +58,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitDebugRule($this); } + + public function __toString(): string + { + return '@debug ' . $this->expression . ';'; + } } diff --git a/src/Ast/Sass/Statement/Declaration.php b/src/Ast/Sass/Statement/Declaration.php index 711be0d1..d12d44ca 100644 --- a/src/Ast/Sass/Statement/Declaration.php +++ b/src/Ast/Sass/Statement/Declaration.php @@ -120,4 +120,24 @@ 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) . '}'; + } } diff --git a/src/Ast/Sass/Statement/EachRule.php b/src/Ast/Sass/Statement/EachRule.php index 9798f09f..3771812d 100644 --- a/src/Ast/Sass/Statement/EachRule.php +++ b/src/Ast/Sass/Statement/EachRule.php @@ -80,4 +80,9 @@ 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()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/ElseClause.php b/src/Ast/Sass/Statement/ElseClause.php index da176e5b..8ced96ed 100644 --- a/src/Ast/Sass/Statement/ElseClause.php +++ b/src/Ast/Sass/Statement/ElseClause.php @@ -19,4 +19,8 @@ */ final class ElseClause extends IfRuleClause { + public function __toString(): string + { + return '@else {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/ErrorRule.php b/src/Ast/Sass/Statement/ErrorRule.php index aed8827e..8dcf4d16 100644 --- a/src/Ast/Sass/Statement/ErrorRule.php +++ b/src/Ast/Sass/Statement/ErrorRule.php @@ -58,4 +58,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitErrorRule($this); } + + public function __toString(): string + { + return '@error ' . $this->expression . ';'; + } } diff --git a/src/Ast/Sass/Statement/ExtendRule.php b/src/Ast/Sass/Statement/ExtendRule.php index 6b72bdc8..323dab29 100644 --- a/src/Ast/Sass/Statement/ExtendRule.php +++ b/src/Ast/Sass/Statement/ExtendRule.php @@ -76,4 +76,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitExtendRule($this); } + + public function __toString(): string + { + return '@extend ' . $this->selector . ($this->optional ? ' !optional' : '') . ';'; + } } diff --git a/src/Ast/Sass/Statement/ForRule.php b/src/Ast/Sass/Statement/ForRule.php index 20a6ef56..2480e1f3 100644 --- a/src/Ast/Sass/Statement/ForRule.php +++ b/src/Ast/Sass/Statement/ForRule.php @@ -103,4 +103,9 @@ 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()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/FunctionRule.php b/src/Ast/Sass/Statement/FunctionRule.php index 4a958a4e..c872828f 100644 --- a/src/Ast/Sass/Statement/FunctionRule.php +++ b/src/Ast/Sass/Statement/FunctionRule.php @@ -35,4 +35,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitFunctionRule($this); } + + public function __toString(): string + { + return '@function ' . $this->getName() . '(' . $this->getArguments() . ') {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/IfRule.php b/src/Ast/Sass/Statement/IfRule.php index 12fa66af..1525694b 100644 --- a/src/Ast/Sass/Statement/IfRule.php +++ b/src/Ast/Sass/Statement/IfRule.php @@ -88,4 +88,19 @@ 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); + } } diff --git a/src/Ast/Sass/Statement/ImportRule.php b/src/Ast/Sass/Statement/ImportRule.php index 42fc59a5..99e5277e 100644 --- a/src/Ast/Sass/Statement/ImportRule.php +++ b/src/Ast/Sass/Statement/ImportRule.php @@ -62,4 +62,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitImportRule($this); } + + public function __toString(): string + { + return '@import ' . implode(', ', $this->imports) . ';'; + } } diff --git a/src/Ast/Sass/Statement/IncludeRule.php b/src/Ast/Sass/Statement/IncludeRule.php index 74926c48..bb815b34 100644 --- a/src/Ast/Sass/Statement/IncludeRule.php +++ b/src/Ast/Sass/Statement/IncludeRule.php @@ -119,4 +119,22 @@ 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; + } } diff --git a/src/Ast/Sass/Statement/LoudComment.php b/src/Ast/Sass/Statement/LoudComment.php index 074e6fbe..743d9b1d 100644 --- a/src/Ast/Sass/Statement/LoudComment.php +++ b/src/Ast/Sass/Statement/LoudComment.php @@ -49,4 +49,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitLoudComment($this); } + + public function __toString(): string + { + return (string) $this->text; + } } diff --git a/src/Ast/Sass/Statement/MediaRule.php b/src/Ast/Sass/Statement/MediaRule.php index 967e2996..f8467673 100644 --- a/src/Ast/Sass/Statement/MediaRule.php +++ b/src/Ast/Sass/Statement/MediaRule.php @@ -67,4 +67,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitMediaRule($this); } + + public function __toString(): string + { + return '@media ' . $this->query . ' {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/MixinRule.php b/src/Ast/Sass/Statement/MixinRule.php index e4d110a5..ec6ddda7 100644 --- a/src/Ast/Sass/Statement/MixinRule.php +++ b/src/Ast/Sass/Statement/MixinRule.php @@ -65,4 +65,17 @@ 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; + } } diff --git a/src/Ast/Sass/Statement/ReturnRule.php b/src/Ast/Sass/Statement/ReturnRule.php index ed80ea21..3a2ee76c 100644 --- a/src/Ast/Sass/Statement/ReturnRule.php +++ b/src/Ast/Sass/Statement/ReturnRule.php @@ -58,4 +58,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitReturnRule($this); } + + public function __toString(): string + { + return '@return ' . $this->expression . ';'; + } } diff --git a/src/Ast/Sass/Statement/SilentComment.php b/src/Ast/Sass/Statement/SilentComment.php index 532e12a7..ec6f1af8 100644 --- a/src/Ast/Sass/Statement/SilentComment.php +++ b/src/Ast/Sass/Statement/SilentComment.php @@ -55,4 +55,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitSilentComment($this); } + + public function __toString(): string + { + return $this->text; + } } diff --git a/src/Ast/Sass/Statement/StyleRule.php b/src/Ast/Sass/Statement/StyleRule.php index 7d5f7540..aec02deb 100644 --- a/src/Ast/Sass/Statement/StyleRule.php +++ b/src/Ast/Sass/Statement/StyleRule.php @@ -69,4 +69,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitStyleRule($this); } + + public function __toString(): string + { + return $this->selector . ' {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/Stylesheet.php b/src/Ast/Sass/Statement/Stylesheet.php index 97ff5677..95196d4a 100644 --- a/src/Ast/Sass/Statement/Stylesheet.php +++ b/src/Ast/Sass/Statement/Stylesheet.php @@ -118,4 +118,9 @@ public static function parseCss(string $contents, ?LoggerInterface $logger = nul { return (new CssParser($contents, $logger, $sourceUrl))->parse(); } + + public function __toString(): string + { + return implode(' ', $this->getChildren()); + } } diff --git a/src/Ast/Sass/Statement/SupportsRule.php b/src/Ast/Sass/Statement/SupportsRule.php index c3d9f3ee..cfac6bde 100644 --- a/src/Ast/Sass/Statement/SupportsRule.php +++ b/src/Ast/Sass/Statement/SupportsRule.php @@ -62,4 +62,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitSupportsRule($this); } + + public function __toString(): string + { + return '@supports ' . $this->condition . ' {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/Statement/VariableDeclaration.php b/src/Ast/Sass/Statement/VariableDeclaration.php index d7d67eb3..e8d283fd 100644 --- a/src/Ast/Sass/Statement/VariableDeclaration.php +++ b/src/Ast/Sass/Statement/VariableDeclaration.php @@ -147,4 +147,15 @@ 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; + } } diff --git a/src/Ast/Sass/Statement/WarnRule.php b/src/Ast/Sass/Statement/WarnRule.php index 3bfc2d2e..ac0501c1 100644 --- a/src/Ast/Sass/Statement/WarnRule.php +++ b/src/Ast/Sass/Statement/WarnRule.php @@ -58,4 +58,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitWarnRule($this); } + + public function __toString(): string + { + return '@warn ' . $this->expression . ';'; + } } diff --git a/src/Ast/Sass/Statement/WhileRule.php b/src/Ast/Sass/Statement/WhileRule.php index 82efc1ec..e4cb376d 100644 --- a/src/Ast/Sass/Statement/WhileRule.php +++ b/src/Ast/Sass/Statement/WhileRule.php @@ -65,4 +65,9 @@ public function accept(StatementVisitor $visitor) { return $visitor->visitWhileRule($this); } + + public function __toString(): string + { + return '@while ' . $this->condition . ' {' . implode(' ', $this->getChildren()) . '}'; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsAnything.php b/src/Ast/Sass/SupportsCondition/SupportsAnything.php index 53060132..1cacca39 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsAnything.php +++ b/src/Ast/Sass/SupportsCondition/SupportsAnything.php @@ -53,4 +53,9 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + return "($this->contents)"; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsDeclaration.php b/src/Ast/Sass/SupportsCondition/SupportsDeclaration.php index 95694817..46ca4a36 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsDeclaration.php +++ b/src/Ast/Sass/SupportsCondition/SupportsDeclaration.php @@ -83,4 +83,9 @@ 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)"; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsFunction.php b/src/Ast/Sass/SupportsCondition/SupportsFunction.php index 058d866c..b22c238e 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsFunction.php +++ b/src/Ast/Sass/SupportsCondition/SupportsFunction.php @@ -66,4 +66,9 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + return "$this->name($this->arguments)"; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsInterpolation.php b/src/Ast/Sass/SupportsCondition/SupportsInterpolation.php index 75f425ca..7cc832a8 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsInterpolation.php +++ b/src/Ast/Sass/SupportsCondition/SupportsInterpolation.php @@ -52,4 +52,9 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + return '#{' . $this->expression . '}'; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsNegation.php b/src/Ast/Sass/SupportsCondition/SupportsNegation.php index 4f131bb5..eec20e68 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsNegation.php +++ b/src/Ast/Sass/SupportsCondition/SupportsNegation.php @@ -51,4 +51,13 @@ 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; + } } diff --git a/src/Ast/Sass/SupportsCondition/SupportsOperation.php b/src/Ast/Sass/SupportsCondition/SupportsOperation.php index a337e3cf..11425b23 100644 --- a/src/Ast/Sass/SupportsCondition/SupportsOperation.php +++ b/src/Ast/Sass/SupportsCondition/SupportsOperation.php @@ -77,4 +77,18 @@ public function getSpan(): FileSpan { return $this->span; } + + public function __toString(): string + { + return $this->parenthesize($this->left) . ' ' . $this->operator . ' ' . $this->parenthesize($this->right); + } + + private function parenthesize(SupportsCondition $condition): string + { + if ($condition instanceof SupportsNegation || $condition instanceof SupportsOperation && $condition->operator === $this->operator) { + return "($condition)"; + } + + return (string) $condition; + } }