• 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} = 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