Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
18.52% |
10 / 54 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
MappingsGenerator | |
18.52% |
10 / 54 |
|
33.33% |
2 / 6 |
89.90 | |
0.00% |
0 / 1 |
nextSourceFile | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
consumeSource | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
6 | |||
outputSpace | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
outputToken | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
appendNumber | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
getMap | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * Copyright 2022 Wikimedia Foundation |
4 | * |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
6 | * you may not use this file except in compliance with the License. |
7 | * You may obtain a copy of the License at |
8 | * |
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
10 | * |
11 | * Unless required by applicable law or agreed to in writing, software |
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 | * See the License for the specific language governing permissions and |
15 | * limitations under the License. |
16 | * |
17 | * @file |
18 | * @license Apache-2.0 |
19 | * @license MIT |
20 | * @license GPL-2.0-or-later |
21 | * @license LGPL-2.1-or-later |
22 | */ |
23 | |
24 | namespace Wikimedia\Minify; |
25 | |
26 | /** |
27 | * Utility class to generate the "mappings" string of a source map. |
28 | * |
29 | * @internal |
30 | */ |
31 | class MappingsGenerator { |
32 | /** @var string */ |
33 | private $source = ''; |
34 | |
35 | /** @var int The current source file offset in bytes */ |
36 | private $curSourceOffset = 0; |
37 | |
38 | /** @var int The current source file index */ |
39 | private $curSourceFile = -1; |
40 | /** @var int The current source file line number */ |
41 | private $curSourceLine = 0; |
42 | /** @var int The current source file column in UTF-16 code units */ |
43 | private $curSourceColumn = 0; |
44 | /** @var int The current output file line number */ |
45 | private $curOutLine = 0; |
46 | /** @var int The current output file column in UTF-16 code units */ |
47 | private $curOutColumn = 0; |
48 | |
49 | /** @var int The base of the delta encoding for source file index */ |
50 | private $prevSourceFile = 0; |
51 | /** @var int The base of the delta encoding for source file line number */ |
52 | private $prevSourceLine = 0; |
53 | /** @var int The base of the delta encoding for source file line column */ |
54 | private $prevSourceColumn = 0; |
55 | /** @var int The base of the delta encoding for output file line number */ |
56 | private $prevOutLine = 0; |
57 | /** @var int The base of the delta encoding for output file column */ |
58 | private $prevOutColumn = 0; |
59 | |
60 | /** @var bool Whether to omit a leading separator when generating a segment */ |
61 | private $isFirstSegment = true; |
62 | |
63 | /** @var string The accumulated mapping string */ |
64 | private $mappings = ''; |
65 | |
66 | /** @var string The base-64 encoding table */ |
67 | private const BASE64_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; |
68 | |
69 | /** |
70 | * Advance to the next source file. |
71 | * |
72 | * @param string $source The contents of the new file |
73 | */ |
74 | public function nextSourceFile( $source ) { |
75 | $this->source = $source; |
76 | $this->curSourceFile++; |
77 | $this->curSourceOffset = 0; |
78 | $this->curSourceLine = 0; |
79 | $this->curSourceColumn = 0; |
80 | } |
81 | |
82 | /** |
83 | * Advance the source position by the specified number of bytes. |
84 | * |
85 | * @param int $length |
86 | */ |
87 | public function consumeSource( $length ) { |
88 | $newOffset = $this->curSourceOffset + $length; |
89 | $lineCount = substr_count( $this->source, "\n", $this->curSourceOffset, $length ); |
90 | if ( $lineCount ) { |
91 | $lineStartPos = |
92 | strrpos( |
93 | substr( $this->source, $this->curSourceOffset, $length ), |
94 | "\n" |
95 | ) + $this->curSourceOffset + 1; |
96 | $this->curSourceLine += $lineCount; |
97 | $this->curSourceColumn = Utils::getJsLength( |
98 | substr( $this->source, $lineStartPos, $newOffset - $lineStartPos ) ); |
99 | } else { |
100 | $this->curSourceColumn += Utils::getJsLength( |
101 | substr( $this->source, $this->curSourceOffset, $length ) ); |
102 | } |
103 | $this->curSourceOffset = $newOffset; |
104 | } |
105 | |
106 | /** |
107 | * Notify the source map generator of the generated text output, which |
108 | * should not generate a mapping segment. |
109 | * |
110 | * @param string $out |
111 | */ |
112 | public function outputSpace( $out ) { |
113 | $lineCount = substr_count( $out, "\n" ); |
114 | if ( $lineCount ) { |
115 | $lineStartPos = strrpos( $out, "\n" ) + 1; |
116 | $this->curOutLine += $lineCount; |
117 | $this->curOutColumn = Utils::getJsLength( |
118 | substr( $out, $lineStartPos, strlen( $out ) - $lineStartPos ) ); |
119 | } else { |
120 | $this->curOutColumn += Utils::getJsLength( $out ); |
121 | } |
122 | } |
123 | |
124 | /** |
125 | * Notify the source map generator of the generated text output, which |
126 | * should generate a mapping segment. Append the mapping segment to the |
127 | * internal buffer. |
128 | * |
129 | * @param string $out |
130 | */ |
131 | public function outputToken( $out ) { |
132 | $outLineDelta = $this->curOutLine - $this->prevOutLine; |
133 | if ( $outLineDelta > 0 ) { |
134 | $this->mappings .= str_repeat( ';', $outLineDelta ); |
135 | $this->prevOutColumn = 0; |
136 | $this->isFirstSegment = false; |
137 | } elseif ( $this->isFirstSegment ) { |
138 | $this->isFirstSegment = false; |
139 | } else { |
140 | $this->mappings .= ','; |
141 | } |
142 | |
143 | $this->appendNumber( $this->curOutColumn - $this->prevOutColumn ); |
144 | $this->appendNumber( $this->curSourceFile - $this->prevSourceFile ); |
145 | $this->appendNumber( $this->curSourceLine - $this->prevSourceLine ); |
146 | $this->appendNumber( $this->curSourceColumn - $this->prevSourceColumn ); |
147 | |
148 | $this->prevSourceFile = $this->curSourceFile; |
149 | $this->prevOutLine = $this->curOutLine; |
150 | $this->prevOutColumn = $this->curOutColumn; |
151 | $this->prevSourceLine = $this->curSourceLine; |
152 | $this->prevSourceColumn = $this->curSourceColumn; |
153 | |
154 | $this->outputSpace( $out ); |
155 | } |
156 | |
157 | /** |
158 | * Append a VLQ encoded number to the buffer. |
159 | * |
160 | * @param int $n |
161 | */ |
162 | private function appendNumber( $n ) { |
163 | $encoded = ''; |
164 | |
165 | // The sign bit goes in the LSB for some reason |
166 | $vlq = $n < 0 ? ( -$n << 1 ) | 1 : $n << 1; |
167 | |
168 | do { |
169 | $digit = $vlq & 0x1f; |
170 | $vlq >>= 5; |
171 | if ( $vlq > 0 ) { |
172 | $digit |= 0x20; |
173 | } |
174 | $encoded .= self::BASE64_TABLE[$digit]; |
175 | } while ( $vlq > 0 ); |
176 | |
177 | $this->mappings .= $encoded; |
178 | } |
179 | |
180 | /** |
181 | * Get the generated mappings string. |
182 | * |
183 | * @return string |
184 | */ |
185 | public function getMap() { |
186 | return $this->mappings; |
187 | } |
188 | } |