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 ArrayPrototypePush, 72 ArrayPrototypeSlice, 73 ArrayPrototypeSort, 74 ObjectPrototypeHasOwnProperty, 75 StringPrototypeCharAt, 76} = primordials; 77 78const { 79 ERR_INVALID_ARG_TYPE 80} = require('internal/errors').codes; 81 82let base64Map; 83 84const VLQ_BASE_SHIFT = 5; 85const VLQ_BASE_MASK = (1 << 5) - 1; 86const VLQ_CONTINUATION_MASK = 1 << 5; 87 88class StringCharIterator { 89 /** 90 * @constructor 91 * @param {string} string 92 */ 93 constructor(string) { 94 this._string = string; 95 this._position = 0; 96 } 97 98 /** 99 * @return {string} 100 */ 101 next() { 102 return StringPrototypeCharAt(this._string, this._position++); 103 } 104 105 /** 106 * @return {string} 107 */ 108 peek() { 109 return StringPrototypeCharAt(this._string, this._position); 110 } 111 112 /** 113 * @return {boolean} 114 */ 115 hasNext() { 116 return this._position < this._string.length; 117 } 118} 119 120/** 121 * Implements Source Map V3 model. 122 * See https://github.com/google/closure-compiler/wiki/Source-Maps 123 * for format description. 124 * @constructor 125 * @param {string} sourceMappingURL 126 * @param {SourceMapV3} payload 127 */ 128class SourceMap { 129 #payload; 130 #mappings = []; 131 #sources = {}; 132 #sourceContentByURL = {}; 133 134 /** 135 * @constructor 136 * @param {SourceMapV3} payload 137 */ 138 constructor(payload) { 139 if (!base64Map) { 140 const base64Digits = 141 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 142 base64Map = {}; 143 for (let i = 0; i < base64Digits.length; ++i) 144 base64Map[base64Digits[i]] = i; 145 } 146 this.#payload = cloneSourceMapV3(payload); 147 this.#parseMappingPayload(); 148 } 149 150 /** 151 * @return {Object} raw source map v3 payload. 152 */ 153 get payload() { 154 return cloneSourceMapV3(this.#payload); 155 } 156 157 /** 158 * @param {SourceMapV3} mappingPayload 159 */ 160 #parseMappingPayload = () => { 161 if (this.#payload.sections) { 162 this.#parseSections(this.#payload.sections); 163 } else { 164 this.#parseMap(this.#payload, 0, 0); 165 } 166 ArrayPrototypeSort(this.#mappings, compareSourceMapEntry); 167 } 168 169 /** 170 * @param {Array.<SourceMapV3.Section>} sections 171 */ 172 #parseSections = (sections) => { 173 for (let i = 0; i < sections.length; ++i) { 174 const section = sections[i]; 175 this.#parseMap(section.map, section.offset.line, section.offset.column); 176 } 177 } 178 179 /** 180 * @param {number} lineNumber in compiled resource 181 * @param {number} columnNumber in compiled resource 182 * @return {?Array} 183 */ 184 findEntry(lineNumber, columnNumber) { 185 let first = 0; 186 let count = this.#mappings.length; 187 while (count > 1) { 188 const step = count >> 1; 189 const middle = first + step; 190 const mapping = this.#mappings[middle]; 191 if (lineNumber < mapping[0] || 192 (lineNumber === mapping[0] && columnNumber < mapping[1])) { 193 count = step; 194 } else { 195 first = middle; 196 count -= step; 197 } 198 } 199 const entry = this.#mappings[first]; 200 if (!first && entry && (lineNumber < entry[0] || 201 (lineNumber === entry[0] && columnNumber < entry[1]))) { 202 return {}; 203 } else if (!entry) { 204 return {}; 205 } 206 return { 207 generatedLine: entry[0], 208 generatedColumn: entry[1], 209 originalSource: entry[2], 210 originalLine: entry[3], 211 originalColumn: entry[4], 212 name: entry[5], 213 }; 214 } 215 216 /** 217 * @override 218 */ 219 #parseMap(map, lineNumber, columnNumber) { 220 let sourceIndex = 0; 221 let sourceLineNumber = 0; 222 let sourceColumnNumber = 0; 223 let nameIndex = 0; 224 225 const sources = []; 226 const originalToCanonicalURLMap = {}; 227 for (let i = 0; i < map.sources.length; ++i) { 228 const url = map.sources[i]; 229 originalToCanonicalURLMap[url] = url; 230 ArrayPrototypePush(sources, url); 231 this.#sources[url] = true; 232 233 if (map.sourcesContent && map.sourcesContent[i]) 234 this.#sourceContentByURL[url] = map.sourcesContent[i]; 235 } 236 237 const stringCharIterator = new StringCharIterator(map.mappings); 238 let sourceURL = sources[sourceIndex]; 239 while (true) { 240 if (stringCharIterator.peek() === ',') 241 stringCharIterator.next(); 242 else { 243 while (stringCharIterator.peek() === ';') { 244 lineNumber += 1; 245 columnNumber = 0; 246 stringCharIterator.next(); 247 } 248 if (!stringCharIterator.hasNext()) 249 break; 250 } 251 252 columnNumber += decodeVLQ(stringCharIterator); 253 if (isSeparator(stringCharIterator.peek())) { 254 ArrayPrototypePush(this.#mappings, [lineNumber, columnNumber]); 255 continue; 256 } 257 258 const sourceIndexDelta = decodeVLQ(stringCharIterator); 259 if (sourceIndexDelta) { 260 sourceIndex += sourceIndexDelta; 261 sourceURL = sources[sourceIndex]; 262 } 263 sourceLineNumber += decodeVLQ(stringCharIterator); 264 sourceColumnNumber += decodeVLQ(stringCharIterator); 265 266 let name; 267 if (!isSeparator(stringCharIterator.peek())) { 268 nameIndex += decodeVLQ(stringCharIterator); 269 name = map.names?.[nameIndex]; 270 } 271 272 ArrayPrototypePush( 273 this.#mappings, 274 [lineNumber, columnNumber, sourceURL, sourceLineNumber, 275 sourceColumnNumber, name] 276 ); 277 } 278 } 279} 280 281/** 282 * @param {string} char 283 * @return {boolean} 284 */ 285function isSeparator(char) { 286 return char === ',' || char === ';'; 287} 288 289/** 290 * @param {SourceMap.StringCharIterator} stringCharIterator 291 * @return {number} 292 */ 293function decodeVLQ(stringCharIterator) { 294 // Read unsigned value. 295 let result = 0; 296 let shift = 0; 297 let digit; 298 do { 299 digit = base64Map[stringCharIterator.next()]; 300 result += (digit & VLQ_BASE_MASK) << shift; 301 shift += VLQ_BASE_SHIFT; 302 } while (digit & VLQ_CONTINUATION_MASK); 303 304 // Fix the sign. 305 const negative = result & 1; 306 // Use unsigned right shift, so that the 32nd bit is properly shifted to the 307 // 31st, and the 32nd becomes unset. 308 result >>>= 1; 309 if (!negative) { 310 return result; 311 } 312 313 // We need to OR here to ensure the 32nd bit (the sign bit in an Int32) is 314 // always set for negative numbers. If `result` were 1, (meaning `negate` is 315 // true and all other bits were zeros), `result` would now be 0. But -0 316 // doesn't flip the 32nd bit as intended. All other numbers will successfully 317 // set the 32nd bit without issue, so doing this is a noop for them. 318 return -result | (1 << 31); 319} 320 321/** 322 * @param {SourceMapV3} payload 323 * @return {SourceMapV3} 324 */ 325function cloneSourceMapV3(payload) { 326 if (typeof payload !== 'object') { 327 throw new ERR_INVALID_ARG_TYPE('payload', ['Object'], payload); 328 } 329 payload = { ...payload }; 330 for (const key in payload) { 331 if (ObjectPrototypeHasOwnProperty(payload, key) && 332 ArrayIsArray(payload[key])) { 333 payload[key] = ArrayPrototypeSlice(payload[key]); 334 } 335 } 336 return payload; 337} 338 339/** 340 * @param {Array} entry1 source map entry [lineNumber, columnNumber, sourceURL, 341 * sourceLineNumber, sourceColumnNumber] 342 * @param {Array} entry2 source map entry. 343 * @return {number} 344 */ 345function compareSourceMapEntry(entry1, entry2) { 346 const { 0: lineNumber1, 1: columnNumber1 } = entry1; 347 const { 0: lineNumber2, 1: columnNumber2 } = entry2; 348 if (lineNumber1 !== lineNumber2) { 349 return lineNumber1 - lineNumber2; 350 } 351 return columnNumber1 - columnNumber2; 352} 353 354module.exports = { 355 SourceMap 356}; 357