Issues (45)

src/Highlight/Highlight.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * This file is part of Railt package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Railt\SDL\Highlight;
13
14
use Phplrt\Contracts\Source\ReadableInterface;
15
use Phplrt\Position\Position;
16
use Phplrt\Position\PositionInterface;
17
18
/**
19
 * Class Highlight
20
 */
21
final class Highlight implements HighlightInterface
22
{
23
    /**
24
     * @var string
25
     */
26
    public string $lineTemplate = ' %s | %s';
27
28
    /**
29
     * @var int
30
     */
31
    public int $lineSize = 5;
32
33
    /**
34
     * @var string
35
     */
36
    public string $lineDelimiter = \PHP_EOL;
37
38
    /**
39
     * @var string
40
     */
41
    public string $errorHighlight = '^';
42
43
    /**
44
     * @var string
45
     */
46
    public string $eoi = '<EOI>';
47
48
    /**
49
     * @var self|HighlightInterface|null
50
     */
51
    private static ?self $instance = null;
52
53
    /**
54
     * @return static
55
     */
56
    public static function getInstance(): self
57
    {
58
        return self::$instance ?? self::$instance = new self();
59
    }
60
61
    /**
62
     * @param ReadableInterface $file
63
     * @param PositionInterface $from
64
     * @param PositionInterface $to
65
     * @return string
66
     */
67
    public function render(ReadableInterface $file, PositionInterface $from, PositionInterface $to = null): string
68
    {
69
        $source = $this->getCodeLine($file, $from);
70
71
        return $this->lines([
72
            $this->line($source, $from->getLine()),
73
            $this->error($from->getColumn(), $this->length($source, $from, $to ?? $from)),
74
        ]);
75
    }
76
77
    /**
78
     * @param ReadableInterface $file
79
     * @param PositionInterface $from
80
     * @param int $length
81
     * @return string
82
     */
83
    public function renderByLength(ReadableInterface $file, PositionInterface $from, int $length = 1): string
84
    {
85
        $to = Position::fromPosition($file, $from->getLine(), $from->getColumn() + $length);
86
87
        return $this->render($file, $from, $to);
88
    }
89
90
    /**
91
     * @param string $source
92
     * @param PositionInterface $from
93
     * @param PositionInterface $to
94
     * @return int
95
     */
96
    private function length(string $source, PositionInterface $from, PositionInterface $to): int
97
    {
98
        $max = \mb_strlen($source) - $from->getColumn();
99
100
        if ($from->getLine() === $to->getLine()) {
101
            return \min($max, $to->getColumn() - $from->getColumn() - 1);
102
        }
103
104
        return $max;
105
    }
106
107
    /**
108
     * @param iterable|string[] $lines
109
     * @return string
110
     */
111
    private function lines(iterable $lines): string
112
    {
113
        $lines = $lines instanceof \Traversable ? \iterator_to_array($lines) : $lines;
114
115
        return \implode($this->lineDelimiter, $lines);
0 ignored issues
show
It seems like $lines can also be of type iterable; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
        return \implode($this->lineDelimiter, /** @scrutinizer ignore-type */ $lines);
Loading history...
116
    }
117
118
    /**
119
     * @param string $text
120
     * @param int|null $line
121
     * @return string
122
     */
123
    private function line(string $text, ?int $line = null): string
124
    {
125
        $prefix = $line === null
126
            ? \str_repeat(' ', $this->lineSize)
127
            : \str_pad($line . '.', $this->lineSize, ' ', \STR_PAD_LEFT);
128
129
        return \sprintf($this->lineTemplate, $prefix, \rtrim($text));
130
    }
131
132
    /**
133
     * @param ReadableInterface $file
134
     * @param PositionInterface $position
135
     * @return string
136
     */
137
    public function getCodeLine(ReadableInterface $file, PositionInterface $position): string
138
    {
139
        [$stream, $i, $line] = [$file->getStream(), 0, ''];
140
141
        while (++$i <= $position->getLine() && ! \feof($stream)) {
142
            $line = \is_string($current = \fgets($stream)) ? $current : $this->eoi;
143
        }
144
145
        return $line;
146
    }
147
148
    /**
149
     * @param int $offset
150
     * @param int $length
151
     * @return string
152
     */
153
    private function error(int $offset, int $length): string
154
    {
155
        $prefix = \str_repeat(' ', \max($offset - 1, 0));
156
        $highlight = \str_repeat($this->errorHighlight, \max($length, 1));
157
158
        return $this->line($prefix . $highlight, null);
159
    }
160
}
161