• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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