aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/scssphp/source-span/src/Highlighter/Highlight.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/scssphp/source-span/src/Highlighter/Highlight.php')
-rw-r--r--vendor/scssphp/source-span/src/Highlighter/Highlight.php205
1 files changed, 205 insertions, 0 deletions
diff --git a/vendor/scssphp/source-span/src/Highlighter/Highlight.php b/vendor/scssphp/source-span/src/Highlighter/Highlight.php
new file mode 100644
index 000000000..16f9ceaea
--- /dev/null
+++ b/vendor/scssphp/source-span/src/Highlighter/Highlight.php
@@ -0,0 +1,205 @@
+<?php
+
+namespace SourceSpan\Highlighter;
+
+use SourceSpan\SimpleSourceLocation;
+use SourceSpan\SimpleSourceSpanWithContext;
+use SourceSpan\SourceSpan;
+use SourceSpan\SourceSpanWithContext;
+use SourceSpan\Util;
+
+/**
+ * Information about how to highlight a single section of a source file.
+ *
+ * @internal
+ */
+final class Highlight
+{
+ /**
+ * The section of the source file to highlight.
+ *
+ * This is normalized to make it easier for {@see Highlighter} to work with.
+ */
+ public readonly SourceSpanWithContext $span;
+
+ /**
+ * The label to include inline when highlighting {@see $span}.
+ *
+ * This helps distinguish clarify what each highlight means when multiple are
+ * used in the same message.
+ */
+ public readonly ?string $label;
+
+ public function __construct(
+ SourceSpan $span,
+ private readonly bool $primary = false,
+ ?string $label = null,
+ ) {
+ $this->span = self::normalizeSpan($span);
+ $this->label = $label === null ? null : str_replace("\r\n", "\n", $label);
+ }
+
+ /**
+ * Whether this is the primary span in the highlight.
+ *
+ * The primary span is highlighted with a different character than
+ * non-primary spans.
+ */
+ public function isPrimary(): bool
+ {
+ return $this->primary;
+ }
+
+ private static function normalizeSpan(SourceSpan $span): SourceSpanWithContext
+ {
+ $newSpan = self::normalizeContext($span);
+ $newSpan = self::normalizeNewlines($newSpan);
+ $newSpan = self::normalizeTrailingNewline($newSpan);
+
+ return self::normalizeEndOfLine($newSpan);
+ }
+
+ /**
+ * Normalizes $span to ensure that it's a {@see SourceSpanWithContext} whose
+ * context actually contains its text at the expected column.
+ *
+ * If it's not already a {@see SourceSpanWithContext}, adjust the start and end
+ * locations' line and column fields so that the highlighter can assume they
+ * match up with the context.
+ */
+ private static function normalizeContext(SourceSpan $span): SourceSpanWithContext
+ {
+ if ($span instanceof SourceSpanWithContext && Util::findLineStart($span->getContext(), $span->getText(), $span->getStart()->getColumn()) !== null) {
+ return $span;
+ }
+
+ return new SimpleSourceSpanWithContext(
+ new SimpleSourceLocation($span->getStart()->getOffset(), $span->getSourceUrl(), 0, 0),
+ new SimpleSourceLocation($span->getEnd()->getOffset(), $span->getSourceUrl(), substr_count($span->getText(), "\n"), self::lastLineLength($span->getText())),
+ $span->getText(),
+ $span->getText()
+ );
+ }
+
+ /**
+ * Normalizes $span to replace Windows-style newlines with Unix-style
+ * newlines.
+ */
+ private static function normalizeNewlines(SourceSpanWithContext $span): SourceSpanWithContext
+ {
+ $text = $span->getText();
+ if (!str_contains($text, "\r\n")) {
+ return $span;
+ }
+
+ $endOffset = $span->getEnd()->getOffset() - substr_count($text, "\r\n");
+
+ return new SimpleSourceSpanWithContext(
+ $span->getStart(),
+ new SimpleSourceLocation($endOffset, $span->getSourceUrl(), $span->getEnd()->getLine(), $span->getEnd()->getColumn()),
+ str_replace("\r\n", "\n", $text),
+ str_replace("\r\n", "\n", $span->getContext())
+ );
+ }
+
+ /**
+ * Normalizes $span to remove a trailing newline from `$span->getContext()`.
+ *
+ * If necessary, also adjust `$span->getEnd()` so that it doesn't point past where
+ * the trailing newline used to be.
+ */
+ private static function normalizeTrailingNewline(SourceSpanWithContext $span): SourceSpanWithContext
+ {
+ if (!str_ends_with($span->getContext(), "\n")) {
+ return $span;
+ }
+
+ // If there's a full blank line on the end of `$span->getContext()`, it's probably
+ // significant, so we shouldn't trim it.
+ if (str_ends_with($span->getText(), "\n\n")) {
+ return $span;
+ }
+
+ $context = substr($span->getContext(), 0, -1);
+ $text = $span->getText();
+ $start = $span->getStart();
+ $end = $span->getEnd();
+
+ if (str_ends_with($text, "\n") && self::isTextAtEndOfContext($span)) {
+ $text = substr($text, 0, -1);
+
+ if ($text === '') {
+ $end = $start;
+ } else {
+ $end = new SimpleSourceLocation(
+ $end->getOffset() - 1,
+ $span->getSourceUrl(),
+ $end->getLine() - 1,
+ self::lastLineLength($context)
+ );
+ $start = $span->getStart()->getOffset() === $span->getEnd()->getOffset() ? $end : $span->getStart();
+ }
+ }
+
+ return new SimpleSourceSpanWithContext($start, $end, $text, $context);
+ }
+
+ /**
+ * Normalizes $span so that the end location is at the end of a line rather
+ * than at the beginning of the next line.
+ */
+ private static function normalizeEndOfLine(SourceSpanWithContext $span): SourceSpanWithContext
+ {
+ if ($span->getEnd()->getColumn() !== 0) {
+ return $span;
+ }
+
+ if ($span->getEnd()->getLine() === $span->getStart()->getLine()) {
+ return $span;
+ }
+
+ $text = substr($span->getText(), 0, -1);
+
+ return new SimpleSourceSpanWithContext(
+ $span->getStart(),
+ new SimpleSourceLocation(
+ $span->getEnd()->getOffset() - 1,
+ $span->getSourceUrl(),
+ $span->getEnd()->getLine() - 1,
+ \strlen($text) - Util::lastIndexOf($text, "\n") - 1
+ ),
+ $text,
+ // If the context also ends with a newline, it's possible that we don't
+ // have the full context for that line, so we shouldn't print it at all.
+ str_ends_with($span->getContext(), "\n") ? substr($span->getContext(), 0, -1) : $span->getContext()
+ );
+ }
+
+ /**
+ * Returns the length of the last line in $text, whether or not it ends in a
+ * newline.
+ */
+ private static function lastLineLength(string $text): int
+ {
+ if ($text === '') {
+ return 0;
+ }
+
+ if ($text[\strlen($text) - 1] === '\n') {
+ return \strlen($text) === 1 ? 0 : \strlen($text) - Util::lastIndexOf($text, "\n", \strlen($text) - 2) - 1;
+ }
+
+ return \strlen($text) - Util::lastIndexOf($text, "\n") - 1;
+ }
+
+ /**
+ * Returns whether $span's text runs all the way to the end of its context.
+ */
+ private static function isTextAtEndOfContext(SourceSpanWithContext $span): bool
+ {
+ $lineStart = Util::findLineStart($span->getContext(), $span->getText(), $span->getStart()->getColumn());
+ \assert($lineStart !== null);
+
+ return $lineStart + $span->getStart()->getColumn() + $span->getLength() === \strlen($span->getContext());
+ }
+}