Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Spatie\LaravelIgnition\Views;
4
 
5
use Illuminate\View\Compilers\BladeCompiler;
6
use Throwable;
7
 
8
class BladeSourceMapCompiler
9
{
10
    protected BladeCompiler $bladeCompiler;
11
 
12
    public function __construct()
13
    {
14
        $this->bladeCompiler = app('blade.compiler');
15
    }
16
 
17
    public function detectLineNumber(string $filename, int $compiledLineNumber): int
18
    {
19
        $map = $this->compileSourcemap((string)file_get_contents($filename));
20
 
21
        return $this->findClosestLineNumberMapping($map, $compiledLineNumber);
22
    }
23
 
24
    protected function compileSourcemap(string $value): string
25
    {
26
        try {
27
            $value = $this->addEchoLineNumbers($value);
28
 
29
            $value = $this->addStatementLineNumbers($value);
30
 
31
            $value = $this->addBladeComponentLineNumbers($value);
32
 
33
            $value = $this->bladeCompiler->compileString($value);
34
 
35
            return $this->trimEmptyLines($value);
36
        } catch (Throwable $e) {
37
            report($e);
38
 
39
            return $value;
40
        }
41
    }
42
 
43
    protected function addEchoLineNumbers(string $value): string
44
    {
45
        $echoPairs = [['{{', '}}'], ['{{{', '}}}'], ['{!!', '!!}']];
46
 
47
        foreach ($echoPairs as $pair) {
48
            // Matches {{ $value }}, {!! $value !!} and  {{{ $value }}} depending on $pair
49
            $pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $pair[0], $pair[1]);
50
 
51
            if (preg_match_all($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
52
                foreach (array_reverse($matches[0]) as $match) {
53
                    $position = mb_strlen(substr($value, 0, $match[1]));
54
 
55
                    $value = $this->insertLineNumberAtPosition($position, $value);
56
                }
57
            }
58
        }
59
 
60
        return $value;
61
    }
62
 
63
    protected function addStatementLineNumbers(string $value): string
64
    {
65
        // Matches @bladeStatements() like @if, @component(...), @etc;
66
        $shouldInsertLineNumbers = preg_match_all(
67
            '/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x',
68
            $value,
69
            $matches,
70
            PREG_OFFSET_CAPTURE
71
        );
72
 
73
        if ($shouldInsertLineNumbers) {
74
            foreach (array_reverse($matches[0]) as $match) {
75
                $position = mb_strlen(substr($value, 0, $match[1]));
76
 
77
                $value = $this->insertLineNumberAtPosition($position, $value);
78
            }
79
        }
80
 
81
        return $value;
82
    }
83
 
84
    protected function addBladeComponentLineNumbers(string $value): string
85
    {
86
        // Matches the start of `<x-blade-component`
87
        $shouldInsertLineNumbers = preg_match_all(
88
            '/<\s*x[-:]([\w\-:.]*)/mx',
89
            $value,
90
            $matches,
91
            PREG_OFFSET_CAPTURE
92
        );
93
 
94
        if ($shouldInsertLineNumbers) {
95
            foreach (array_reverse($matches[0]) as $match) {
96
                $position = mb_strlen(substr($value, 0, $match[1]));
97
 
98
                $value = $this->insertLineNumberAtPosition($position, $value);
99
            }
100
        }
101
 
102
        return $value;
103
    }
104
 
105
    protected function insertLineNumberAtPosition(int $position, string $value): string
106
    {
107
        $before = mb_substr($value, 0, $position);
108
        $lineNumber = count(explode("\n", $before));
109
 
110
        return mb_substr($value, 0, $position)."|---LINE:{$lineNumber}---|".mb_substr($value, $position);
111
    }
112
 
113
    protected function trimEmptyLines(string $value): string
114
    {
115
        $value = preg_replace('/^\|---LINE:([0-9]+)---\|$/m', '', $value);
116
 
117
        return ltrim((string)$value, PHP_EOL);
118
    }
119
 
120
    protected function findClosestLineNumberMapping(string $map, int $compiledLineNumber): int
121
    {
122
        $map = explode("\n", $map);
123
 
124
        // Max 20 lines between compiled and source line number.
125
        // Blade components can span multiple lines and the compiled line number is often
126
        // a couple lines below the source-mapped `<x-component>` code.
127
        $maxDistance = 20;
128
 
129
        $pattern = '/\|---LINE:(?P<line>[0-9]+)---\|/m';
130
        $lineNumberToCheck = $compiledLineNumber - 1;
131
 
132
        while (true) {
133
            if ($lineNumberToCheck < $compiledLineNumber - $maxDistance) {
134
                // Something wrong. Return the $compiledLineNumber (unless it's out of range)
135
                return min($compiledLineNumber, count($map));
136
            }
137
 
138
            if (preg_match($pattern, $map[$lineNumberToCheck] ?? '', $matches)) {
139
                return (int)$matches['line'];
140
            }
141
 
142
            $lineNumberToCheck--;
143
        }
144
    }
145
}