1// This file is a modified version of: 2// https://cs.chromium.org/chromium/src/v8/tools/SourceMap.js?rcl=dd10454c1d 3// from the V8 codebase. Logic specific to WebInspector is removed and linting 4// is made to match the Node.js style guide. 5 6// Copyright 2013 the V8 project authors. All rights reserved. 7// Redistribution and use in source and binary forms, with or without 8// modification, are permitted provided that the following conditions are 9// met: 10// 11// * Redistributions of source code must retain the above copyright 12// notice, this list of conditions and the following disclaimer. 13// * Redistributions in binary form must reproduce the above 14// copyright notice, this list of conditions and the following 15// disclaimer in the documentation and/or other materials provided 16// with the distribution. 17// * Neither the name of Google Inc. nor the names of its 18// contributors may be used to endorse or promote products derived 19// from this software without specific prior written permission. 20// 21// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 33// This is a copy from blink dev tools, see: 34// http://src.chromium.org/viewvc/blink/trunk/Source/devtools/front_end/SourceMap.js 35// revision: 153407 36 37/* 38 * Copyright (C) 2012 Google Inc. All rights reserved. 39 * 40 * Redistribution and use in source and binary forms, with or without 41 * modification, are permitted provided that the following conditions are 42 * met: 43 * 44 * * Redistributions of source code must retain the above copyright 45 * notice, this list of conditions and the following disclaimer. 46 * * Redistributions in binary form must reproduce the above 47 * copyright notice, this list of conditions and the following disclaimer 48 * in the documentation and/or other materials provided with the 49 * distribution. 50 * * Neither the name of Google Inc. nor the names of its 51 * contributors may be used to endorse or promote products derived from 52 * this software without specific prior written permission. 53 * 54 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 55 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 56 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 57 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 58 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 59 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 60 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 61 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 62 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 63 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 64 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 65 */ 66 67'use strict'; 68 69const { 70 ArrayIsArray 71} = primordials; 72 73const { 74 ERR_INVALID_ARG_TYPE 75} = require('internal/errors').codes; 76 77let base64Map; 78 79const VLQ_BASE_SHIFT = 5; 80const VLQ_BASE_MASK = (1 << 5) - 1; 81const VLQ_CONTINUATION_MASK = 1 << 5; 82 83class StringCharIterator { 84 /** 85 * @constructor 86 * @param {string} string 87 */ 88 constructor(string) { 89 this._string = string; 90 this._position = 0; 91 } 92 93 /** 94 * @return {string} 95 */ 96 next() { 97 return this._string.charAt(this._position++); 98 } 99 100 /** 101 * @return {string} 102 */ 103 peek() { 104 return this._string.charAt(this._position); 105 } 106 107 /** 108 * @return {boolean} 109 */ 110 hasNext() { 111 return this._position < this._string.length; 112 } 113} 114 115/** 116 * Implements Source Map V3 model. 117 * See https://github.com/google/closure-compiler/wiki/Source-Maps 118 * for format description. 119 * @constructor 120 * @param {string} sourceMappingURL 121 * @param {SourceMapV3} payload 122 */ 123class SourceMap { 124 #payload; 125 #mappings = []; 126 #sources = {}; 127 #sourceContentByURL = {}; 128 129 /** 130 * @constructor 131 * @param {SourceMapV3} payload 132 */ 133 constructor(payload) { 134 if (!base64Map) { 135 const base64Digits = 136 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 137 base64Map = {}; 138 for (let i = 0; i < base64Digits.length; ++i) 139 base64Map[base64Digits[i]] = i; 140 } 141 this.#payload = cloneSourceMapV3(payload); 142 this.#parseMappingPayload(); 143 } 144 145 /** 146 * @return {Object} raw source map v3 payload. 147 */ 148 get payload() { 149 return cloneSourceMapV3(this.#payload); 150 } 151 152 /** 153 * @param {SourceMapV3} mappingPayload 154 */ 155 #parseMappingPayload = () => { 156 if (this.#payload.sections) { 157 this.#parseSections(this.#payload.sections); 158 } else { 159 this.#parseMap(this.#payload, 0, 0); 160 } 161 this.#mappings.sort(compareSourceMapEntry); 162 } 163 164 /** 165 * @param {Array.<SourceMapV3.Section>} sections 166 */ 167 #parseSections = (sections) => { 168 for (let i = 0; i < sections.length; ++i) { 169 const section = sections[i]; 170 this.#parseMap(section.map, section.offset.line, section.offset.column); 171 } 172 } 173 174 /** 175 * @param {number} lineNumber in compiled resource 176 * @param {number} columnNumber in compiled resource 177 * @return {?Array} 178 */ 179 findEntry(lineNumber, columnNumber) { 180 let first = 0; 181 let count = this.#mappings.length; 182 while (count > 1) { 183 const step = count >> 1; 184 const middle = first + step; 185 const mapping = this.#mappings[middle]; 186 if (lineNumber < mapping[0] || 187 (lineNumber === mapping[0] && columnNumber < mapping[1])) { 188 count = step; 189 } else { 190 first = middle; 191 count -= step; 192 } 193 } 194 const entry = this.#mappings[first]; 195 if (!first && entry && (lineNumber < entry[0] || 196 (lineNumber === entry[0] && columnNumber < entry[1]))) { 197 return {}; 198 } else if (!entry) { 199 return {}; 200 } 201 return { 202 generatedLine: entry[0], 203 generatedColumn: entry[1], 204 originalSource: entry[2], 205 originalLine: entry[3], 206 originalColumn: entry[4] 207 }; 208 } 209 210 /** 211 * @override 212 */ 213 #parseMap = (map, lineNumber, columnNumber) => { 214 let sourceIndex = 0; 215 let sourceLineNumber = 0; 216 let sourceColumnNumber = 0; 217 218 const sources = []; 219 const originalToCanonicalURLMap = {}; 220 for (let i = 0; i < map.sources.length; ++i) { 221 const url = map.sources[i]; 222 originalToCanonicalURLMap[url] = url; 223 sources.push(url); 224 this.#sources[url] = true; 225 226 if (map.sourcesContent && map.sourcesContent[i]) 227 this.#sourceContentByURL[url] = map.sourcesContent[i]; 228 } 229 230 const stringCharIterator = new StringCharIterator(map.mappings); 231 let sourceURL = sources[sourceIndex]; 232 233 while (true) { 234 if (stringCharIterator.peek() === ',') 235 stringCharIterator.next(); 236 else { 237 while (stringCharIterator.peek() === ';') { 238 lineNumber += 1; 239 columnNumber = 0; 240 stringCharIterator.next(); 241 } 242 if (!stringCharIterator.hasNext()) 243 break; 244 } 245 246 columnNumber += decodeVLQ(stringCharIterator); 247 if (isSeparator(stringCharIterator.peek())) { 248 this.#mappings.push([lineNumber, columnNumber]); 249 continue; 250 } 251 252 const sourceIndexDelta = decodeVLQ(stringCharIterator); 253 if (sourceIndexDelta) { 254 sourceIndex += sourceIndexDelta; 255 sourceURL = sources[sourceIndex]; 256 } 257 sourceLineNumber += decodeVLQ(stringCharIterator); 258 sourceColumnNumber += decodeVLQ(stringCharIterator); 259 if (!isSeparator(stringCharIterator.peek())) 260 // Unused index into the names list. 261 decodeVLQ(stringCharIterator); 262 263 this.#mappings.push([lineNumber, columnNumber, sourceURL, 264 sourceLineNumber, sourceColumnNumber]); 265 } 266 }; 267} 268 269/** 270 * @param {string} char 271 * @return {boolean} 272 */ 273function isSeparator(char) { 274 return char === ',' || char === ';'; 275} 276 277/** 278 * @param {SourceMap.StringCharIterator} stringCharIterator 279 * @return {number} 280 */ 281function decodeVLQ(stringCharIterator) { 282 // Read unsigned value. 283 let result = 0; 284 let shift = 0; 285 let digit; 286 do { 287 digit = base64Map[stringCharIterator.next()]; 288 result += (digit & VLQ_BASE_MASK) << shift; 289 shift += VLQ_BASE_SHIFT; 290 } while (digit & VLQ_CONTINUATION_MASK); 291 292 // Fix the sign. 293 const negative = result & 1; 294 // Use unsigned right shift, so that the 32nd bit is properly shifted to the 295 // 31st, and the 32nd becomes unset. 296 result >>>= 1; 297 if (!negative) { 298 return result; 299 } 300 301 // We need to OR here to ensure the 32nd bit (the sign bit in an Int32) is 302 // always set for negative numbers. If `result` were 1, (meaning `negate` is 303 // true and all other bits were zeros), `result` would now be 0. But -0 304 // doesn't flip the 32nd bit as intended. All other numbers will successfully 305 // set the 32nd bit without issue, so doing this is a noop for them. 306 return -result | (1 << 31); 307} 308 309/** 310 * @param {SourceMapV3} payload 311 * @return {SourceMapV3} 312 */ 313function cloneSourceMapV3(payload) { 314 if (typeof payload !== 'object') { 315 throw new ERR_INVALID_ARG_TYPE('payload', ['Object'], payload); 316 } 317 payload = { ...payload }; 318 for (const key in payload) { 319 if (payload.hasOwnProperty(key) && ArrayIsArray(payload[key])) { 320 payload[key] = payload[key].slice(0); 321 } 322 } 323 return payload; 324} 325 326/** 327 * @param {Array} entry1 source map entry [lineNumber, columnNumber, sourceURL, 328 * sourceLineNumber, sourceColumnNumber] 329 * @param {Array} entry2 source map entry. 330 * @return {number} 331 */ 332function compareSourceMapEntry(entry1, entry2) { 333 const [lineNumber1, columnNumber1] = entry1; 334 const [lineNumber2, columnNumber2] = entry2; 335 if (lineNumber1 !== lineNumber2) { 336 return lineNumber1 - lineNumber2; 337 } 338 return columnNumber1 - columnNumber2; 339} 340 341module.exports = { 342 SourceMap 343}; 344