diff options
Diffstat (limited to 'vendor/scssphp/source-span/src/Util.php')
-rw-r--r-- | vendor/scssphp/source-span/src/Util.php | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/vendor/scssphp/source-span/src/Util.php b/vendor/scssphp/source-span/src/Util.php new file mode 100644 index 000000000..ccddb009c --- /dev/null +++ b/vendor/scssphp/source-span/src/Util.php @@ -0,0 +1,358 @@ +<?php + +namespace SourceSpan; + +use League\Uri\BaseUri; +use League\Uri\Contracts\UriInterface; + +/** + * @internal + */ +final class Util +{ + /** + * @param iterable<object> $iter + */ + public static function isAllTheSame(iterable $iter): bool + { + $previousValue = null; + + foreach ($iter as $value) { + if ($previousValue === null) { + $previousValue = $value; + continue; + } + + if (!self::isSame($value, $previousValue)) { + return false; + } + } + + return true; + } + + /** + * Returns whether 2 objects are the same, considering URIs as the same by equality rather than reference. + */ + public static function isSame(object $object1, object $object2): bool + { + if ($object1 === $object2) { + return true; + } + + if ($object1 instanceof UriInterface && $object2 instanceof UriInterface) { + return $object1->toString() === $object2->toString(); + } + + return false; + } + + /** + * Returns whether $span covers multiple lines. + */ + public static function isMultiline(SourceSpan $span): bool + { + return $span->getStart()->getLine() !== $span->getEnd()->getLine(); + } + + /** + * Sets the first `null` element of $list to $element. + * + * @template E + * @param list<E|null> $list + * @param E $element + */ + public static function replaceFirstNull(array &$list, $element): void + { + $index = array_search(null, $list, true); + + if ($index === false) { + throw new \InvalidArgumentException('The list contains no null elements.'); + } + + // @phpstan-ignore parameterByRef.type + $list[$index] = $element; + \assert(array_is_list($list)); + } + + /** + * Sets the element of $list that currently contains $element to `null`. + * + * @template E + * @param list<E|null> $list + * @param E $element + */ + public static function replaceWithNull(array &$list, $element): void + { + $index = array_search($element, $list, true); + + if ($index === false) { + throw new \InvalidArgumentException('The list contains no matching elements.'); + } + + // @phpstan-ignore parameterByRef.type + $list[$index] = null; + \assert(array_is_list($list)); + } + + /** + * Finds a line in $context containing $text at the specified column. + * + * Returns the index in $context where that line begins, or null if none + * exists. + */ + public static function findLineStart(string $context, string $text, int $column): ?int + { + // If the text is empty, we just want to find the first line that has at least + // $column characters. + if ($text === '') { + $beginningOfLine = 0; + + while (true) { + $index = strpos($context, "\n", $beginningOfLine); + + if ($index === false) { + return \strlen($context) - $beginningOfLine >= $column ? $beginningOfLine : null; + } + + if ($index - $beginningOfLine >= $column) { + return $beginningOfLine; + } + + $beginningOfLine = $index + 1; + } + } + + $index = strpos($context, $text); + + while ($index !== false) { + // Start looking before $index in case $text starts with a newline. + $lineStart = $index === 0 ? 0 : Util::lastIndexOf($context, "\n", $index - 1) + 1; + $textColumn = $index - $lineStart; + + if ($column === $textColumn) { + return $lineStart; + } + + $index = strpos($context, $text, $index + 1); + } + + return null; + } + + /** + * Returns a two-element list containing the start and end locations of the + * span from $start bytes (inclusive) to $end bytes (exclusive) + * after the beginning of $span. + * + * @return array{SourceLocation, SourceLocation} + */ + public static function subspanLocations(SourceSpan $span, int $start, ?int $end = null): array + { + $text = $span->getText(); + $startLocation = $span->getStart(); + $line = $startLocation->getLine(); + $column = $startLocation->getColumn(); + + // Adjust $line and $column as necessary if the character at $i in $text + // is a newline. + $consumeCodePoint = function (int $i) use ($text, &$line, &$column) { + $codeUnit = $text[$i]; + + if ( + $codeUnit === "\n" || + // A carriage return counts as a newline, but only if it's not + // followed by a line feed. + ($codeUnit === "\r" && ($i + 1 === \strlen($text) || $text[$i + 1] !== "\n")) + ) { + $line += 1; + $column = 0; + } else { + $column += 1; + } + }; + + for ($i = 0; $i < $start; $i++) { + $consumeCodePoint($i); + } + + $newStartLocation = new SimpleSourceLocation($startLocation->getOffset() + $start, $span->getSourceUrl(), $line, $column); + + if ($end === null || $end === $span->getLength()) { + $newEndLocation = $span->getEnd(); + } elseif ($end === $start) { + $newEndLocation = $newStartLocation; + } else { + for ($i = $start; $i < $end; $i++) { + $consumeCodePoint($i); + } + + $newEndLocation = new SimpleSourceLocation($startLocation->getOffset() + $end, $span->getSourceUrl(), $line, $column); + } + + return [$newStartLocation, $newEndLocation]; + } + + /** + * The starting position of the last match $needle in this string. + * + * Finds a match of $needle by searching backward starting at $start. + * Returns -1 if $needle could not be found in this string. + * If $start is omitted, search starts from the end of the string. + */ + public static function lastIndexOf(string $string, string $needle, ?int $start = null): int + { + if ($start === null || $start === \strlen($string)) { + $position = strrpos($string, $needle); + } else { + if ($start < 0) { + throw new \InvalidArgumentException("Start must be a non-negative integer"); + } + + if ($start > \strlen($string)) { + throw new \InvalidArgumentException("Start must not be greater than the length of the string"); + } + + $position = strrpos($string, $needle, $start - \strlen($string)); + } + + return $position === false ? -1 : $position; + } + + /** + * Returns the text of the string from $start to $end (exclusive). + * + * If $end isn't passed, it defaults to the end of the string. + */ + public static function substring(string $text, int $start, ?int $end = null): string + { + if ($end === null) { + return substr($text, $start); + } + + if ($end < $start) { + $length = 0; + } else { + $length = $end - $start; + } + + return substr($text, $start, $length); + } + + public static function isSameUrl(?UriInterface $url1, ?UriInterface $url2): bool + { + if ($url1 === null) { + return $url2 === null; + } + + if ($url2 === null) { + return false; + } + + return (string) $url1 === (string) $url2; + } + + /** + * Finds the first index in the list that satisfies the provided $test. + * + * @template E + * + * @param list<E> $list + * @param callable(E): bool $test + */ + public static function indexWhere(array $list, callable $test): ?int + { + foreach ($list as $index => $element) { + if ($test($element)) { + return $index; + } + } + + return null; + } + + /** + * Check that a range represents a slice of an indexable object. + * + * Throws if the range is not valid for an indexable object with + * the given length. + * A range is valid for an indexable object with a given $length + * if `0 <= $start <= $end <= $length`. + * An `end` of `null` is considered equivalent to `length`. + * + * @throws \OutOfRangeException + */ + public static function checkValidRange(int $start, ?int $end, int $length, ?string $startName = null, ?string $endName = null): void + { + if ($start < 0 || $start > $length) { + $startName ??= 'start'; + $startNameDisplay = $startName ? " $startName" : ''; + + throw new \OutOfRangeException("Invalid value:$startNameDisplay must be between 0 and $length: $start."); + } + + if ($end !== null) { + if ($end < $start || $end > $length) { + $endName ??= 'end'; + $endNameDisplay = $endName ? " $endName" : ''; + + throw new \OutOfRangeException("Invalid value:$endNameDisplay must be between $start and $length: $end."); + } + } + } + + /** + * @template T + * + * @param list<T> $list + * + * @return T + */ + public static function listLast(array $list) + { + $count = count($list); + + if ($count === 0) { + throw new \LogicException('The list may not be empty.'); + } + + return $list[$count - 1]; + } + + /** + * Returns a pretty URI for a path + */ + public static function prettyUri(string|UriInterface $path): string + { + if ($path instanceof UriInterface) { + if ($path->getScheme() !== 'file') { + return (string) $path; + } + + $path = self::pathFromUri($path); + } + + $normalizedPath = $path; + $normalizedRootDirectory = getcwd() . '/'; + + if (\DIRECTORY_SEPARATOR === '\\') { + $normalizedRootDirectory = str_replace('\\', '/', $normalizedRootDirectory); + $normalizedPath = str_replace('\\', '/', $path); + } + + if (str_starts_with($normalizedPath, $normalizedRootDirectory)) { + return substr($path, \strlen($normalizedRootDirectory)); + } + + return $path; + } + + private static function pathFromUri(UriInterface $uri): string + { + if (\DIRECTORY_SEPARATOR === '\\') { + return BaseUri::from($uri)->windowsPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + } + + return BaseUri::from($uri)->unixPath() ?? throw new \InvalidArgumentException("Uri $uri must have scheme 'file:'."); + } +} |