Passed
Pull Request — development (#3829)
by Spuds
07:50
created

MappingsGenerator   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 74
c 1
b 0
f 0
dl 0
loc 156
rs 10
wmc 13

6 Methods

Rating   Name   Duplication   Size   Complexity  
A outputToken() 0 24 3
A outputSpace() 0 9 2
A consumeSource() 0 17 2
A appendNumber() 0 16 4
A getMap() 0 2 1
A nextSourceFile() 0 6 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
}
189