aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php')
-rw-r--r--vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php242
1 files changed, 242 insertions, 0 deletions
diff --git a/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php b/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php
new file mode 100644
index 000000000..4098f328f
--- /dev/null
+++ b/vendor/scssphp/scssphp/src/Ast/Sass/ArgumentDeclaration.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * SCSSPHP
+ *
+ * @copyright 2012-2020 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://scssphp.github.io/scssphp
+ */
+
+namespace ScssPhp\ScssPhp\Ast\Sass;
+
+use League\Uri\Contracts\UriInterface;
+use ScssPhp\ScssPhp\Exception\SassFormatException;
+use ScssPhp\ScssPhp\Exception\SassScriptException;
+use ScssPhp\ScssPhp\Logger\LoggerInterface;
+use ScssPhp\ScssPhp\Parser\ScssParser;
+use ScssPhp\ScssPhp\Util\Character;
+use ScssPhp\ScssPhp\Util\SpanUtil;
+use ScssPhp\ScssPhp\Util\StringUtil;
+use SourceSpan\FileSpan;
+
+/**
+ * An argument declaration, as for a function or mixin definition.
+ *
+ * @internal
+ */
+final class ArgumentDeclaration implements SassNode
+{
+ /**
+ * @var list<Argument>
+ */
+ private readonly array $arguments;
+
+ private readonly ?string $restArgument;
+
+ private readonly FileSpan $span;
+
+ /**
+ * @param list<Argument> $arguments
+ */
+ public function __construct(array $arguments, FileSpan $span, ?string $restArgument = null)
+ {
+ $this->arguments = $arguments;
+ $this->restArgument = $restArgument;
+ $this->span = $span;
+ }
+
+ public static function createEmpty(FileSpan $span): ArgumentDeclaration
+ {
+ return new self([], $span);
+ }
+
+ /**
+ * Parses an argument declaration from $contents, which should be of the
+ * form `@rule name(args) {`.
+ *
+ * If passed, $url is the name of the file from which $contents comes.
+ *
+ * @throws SassFormatException if parsing fails.
+ */
+ public static function parse(string $contents, ?LoggerInterface $logger = null, ?UriInterface $url = null): ArgumentDeclaration
+ {
+ return (new ScssParser($contents, $logger, $url))->parseArgumentDeclaration();
+ }
+
+ public function isEmpty(): bool
+ {
+ return \count($this->arguments) === 0 && $this->restArgument === null;
+ }
+
+ /**
+ * @return list<Argument>
+ */
+ public function getArguments(): array
+ {
+ return $this->arguments;
+ }
+
+ public function getRestArgument(): ?string
+ {
+ return $this->restArgument;
+ }
+
+ public function getSpan(): FileSpan
+ {
+ return $this->span;
+ }
+
+ /**
+ * Returns {@see $span} expanded to include an identifier immediately before the
+ * declaration, if possible.
+ */
+ public function getSpanWithName(): FileSpan
+ {
+ $text = $this->span->getFile()->getText(0);
+
+ // Move backwards through any whitespace between the name and the arguments.
+ $i = $this->span->getStart()->getOffset() - 1;
+ while ($i > 0 && Character::isWhitespace($text[$i])) {
+ $i--;
+ }
+
+ // Then move backwards through the name itself.
+ if (!Character::isName($text[$i])) {
+ return $this->span;
+ }
+ $i--;
+ while ($i >= 0 && Character::isName($text[$i])) {
+ $i--;
+ }
+
+ // Trim because it's possible that this span is empty (for example, a mixin
+ // may be declared without an argument list).
+ return SpanUtil::trim($this->span->getFile()->span($i + 1, $this->span->getEnd()->getOffset()));
+ }
+
+ /**
+ * @param array<string, mixed> $names Only keys are relevant
+ *
+ * @throws SassScriptException if $positional and $names aren't valid for this argument declaration.
+ */
+ public function verify(int $positional, array $names): void
+ {
+ $nameUsed = 0;
+
+ foreach ($this->arguments as $i => $argument) {
+ if ($i < $positional) {
+ if (isset($names[$argument->getName()])) {
+ $originalName = $this->originalArgumentName($argument->getName());
+ throw new SassScriptException(sprintf('Argument $%s was passed both by position and by name.', $originalName));
+ }
+ } elseif (isset($names[$argument->getName()])) {
+ $nameUsed++;
+ } elseif ($argument->getDefaultValue() === null) {
+ $originalName = $this->originalArgumentName($argument->getName());
+ throw new SassScriptException(sprintf('Missing argument $%s', $originalName));
+ }
+ }
+
+ if ($this->restArgument !== null) {
+ return;
+ }
+
+ if ($positional > \count($this->arguments)) {
+ $message = sprintf(
+ 'Only %d %s%s allowed, but %d %s passed.',
+ \count($this->arguments),
+ empty($names) ? '' : 'positional ',
+ StringUtil::pluralize('argument', \count($this->arguments)),
+ $positional,
+ StringUtil::pluralize('was', $positional, 'were')
+ );
+ throw new SassScriptException($message);
+ }
+
+ if ($nameUsed < \count($names)) {
+ $unknownNames = array_values(array_diff(array_keys($names), array_map(fn($argument) => $argument->getName(), $this->arguments)));
+ \assert(\count($unknownNames) > 0);
+ $message = sprintf(
+ 'No %s named %s.',
+ StringUtil::pluralize('argument', \count($unknownNames)),
+ StringUtil::toSentence(array_map(fn ($name) => '$' . $name, $unknownNames), 'or')
+ );
+ throw new 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 array<string, mixed> $names Only keys are relevant
+ */
+ public function matches(int $positional, array $names): bool
+ {
+ $nameUsed = 0;
+
+ foreach ($this->arguments as $i => $argument) {
+ if ($i < $positional) {
+ if (isset($names[$argument->getName()])) {
+ return false;
+ }
+ } elseif (isset($names[$argument->getName()])) {
+ $nameUsed++;
+ } elseif ($argument->getDefaultValue() === null) {
+ return false;
+ }
+ }
+
+ if ($this->restArgument !== null) {
+ return true;
+ }
+
+ if ($positional > \count($this->arguments)) {
+ return false;
+ }
+
+ if ($nameUsed < \count($names)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function __toString(): string
+ {
+ $parts = [];
+ foreach ($this->arguments as $arg) {
+ $parts[] = "\$$arg";
+ }
+ if ($this->restArgument !== null) {
+ $parts[] = "\$$this->restArgument...";
+ }
+
+ return implode(', ', $parts);
+ }
+}