Issues (45)

src/Exception/GraphQLException.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\Exception;
13
14
use Phplrt\Contracts\Lexer\TokenInterface;
15
use Phplrt\Contracts\Source\FileInterface;
16
use Phplrt\Contracts\Source\ReadableInterface;
17
use Phplrt\Position\Position;
18
use Phplrt\Position\PositionInterface;
19
use Railt\SDL\Frontend\Ast\Node;
20
use Railt\SDL\Highlight\Highlight;
21
22
/**
23
 * Class GraphQLException
24
 */
25
class GraphQLException extends \LogicException
26
{
27
    /**
28
     * @var string[]
29
     */
30
    private const INTERNAL_NAMESPACES = [
31
        'Railt',
32
        'Phplrt',
33
    ];
34
35
    /**
36
     * @var PositionInterface
37
     */
38
    private PositionInterface $position;
39
40
    /**
41
     * @param string $message
42
     * @param Node $ast
43
     * @param \Throwable|null $prev
44
     * @return static
45
     */
46
    public static function fromAst(string $message, Node $ast = null, \Throwable $prev = null): self
47
    {
48
        $instance = new static($message, $prev ? $prev->getCode() : 0, $prev);
49
        $instance->position = new Position(0, $instance->getLine(), 0);
50
51
        if ($ast && $ast->loc) {
52
            $instance->position = $ast->loc->getStartPosition();
53
54
            self::patch($instance, $ast->loc->source,
55
                fn(Highlight $hl): string => $hl->render($ast->loc->source, $ast->loc->getStartPosition(),
0 ignored issues
show
A parse error occurred: Syntax error, unexpected ':', expecting T_DOUBLE_ARROW on line 55 at column 33
Loading history...
56
                    $ast->loc->getEndPosition())
57
            );
58
        }
59
60
        return $instance;
61
    }
62
63
    /**
64
     * @param GraphQLException $instance
65
     * @param ReadableInterface $source
66
     * @param \Closure $message
67
     * @return void
68
     */
69
    private static function patch(self $instance, ReadableInterface $source, \Closure $message): void
70
    {
71
        if ($source instanceof FileInterface) {
72
            $instance->file = $source->getPathname();
73
            $instance->line = $instance->position->getLine();
74
        } else {
75
            [$instance->file, $instance->line] = self::extractFromTrace(
76
                \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS)
77
            );
78
        }
79
80
        $instance->message .= \PHP_EOL . $message(Highlight::getInstance());
81
    }
82
83
    /**
84
     * @param array $trace
85
     * @return array
86
     */
87
    private static function extractFromTrace(array $trace): array
88
    {
89
        ['file' => $file, 'line' => $line] = \reset($trace);
90
91
        do {
92
            $item = \array_shift($trace);
93
94
            if (isset($item['file'], $item['line'])) {
95
                ['file' => $file, 'line' => $line] = $item;
96
            }
97
98
            foreach (self::INTERNAL_NAMESPACES as $namespace) {
99
                if (\strpos($item['class'] ?? '', $namespace) === 0) {
100
                    continue 2;
101
                }
102
            }
103
104
            break;
105
        } while (\count($trace));
106
107
        return [$file, $line];
108
    }
109
110
    /**
111
     * @param string $message
112
     * @param ReadableInterface $source
113
     * @param TokenInterface $token
114
     * @param \Throwable|null $prev
115
     * @return static
116
     */
117
    public static function fromToken(
118
        string $message,
119
        ReadableInterface $source,
120
        TokenInterface $token,
121
        \Throwable $prev = null
122
    ): self {
123
        [$offset, $length] = [$token->getOffset(), $token->getBytes()];
124
125
        return static::fromOffset($message, $source, $offset, $length, $prev);
126
    }
127
128
    /**
129
     * @param string $message
130
     * @param ReadableInterface $source
131
     * @param int $offset
132
     * @param int $length
133
     * @param \Throwable|null $prev
134
     * @return static
135
     */
136
    public static function fromOffset(
137
        string $message,
138
        ReadableInterface $source,
139
        int $offset,
140
        int $length = 1,
141
        \Throwable $prev = null
142
    ): self {
143
        $instance = new static($message, $prev ? $prev->getCode() : 0, $prev);
144
        $instance->position = Position::fromOffset($source, $offset);
145
146
        self::patch($instance, $source,
147
            fn(Highlight $hl): string => $hl->renderByLength($source, $instance->position, $length + 1)
148
        );
149
150
        return $instance;
151
    }
152
153
    /**
154
     * @return PositionInterface
155
     */
156
    public function getPosition(): PositionInterface
157
    {
158
        return $this->position;
159
    }
160
}
161