Permalink
Browse files

Initial sourcemap parsing code

  • Loading branch information...
1 parent 5ae23cb commit 7d296413c5ff7619868b064ee125267c26d6782c @dcramer dcramer committed Jan 18, 2013
@@ -0,0 +1,113 @@
+"""
+sentry.utils.sourcemaps
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Originally based on https://github.com/martine/python-sourcemap
+
+:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details.
+:license: BSD, see LICENSE for more details.
+"""
+
+import bisect
+from collections import namedtuple
+from sentry.utils import json
+
+
+SourceMap = namedtuple('SourceMap', ['dst_line', 'dst_col', 'src', 'src_line', 'src_col', 'name'])
+SourceMapIndex = namedtuple('SourceMapIndex', ['state_list', 'key_list'])
+
+# Mapping of base64 letter -> integer value.
+B64 = dict(
+ (c, i) for i, c in
+ enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')
+)
+
+
+def parse_vlq(segment):
+ """
+ Parse a string of VLQ-encoded data.
+
+ Returns:
+ a list of integers.
+ """
+
+ values = []
+
+ cur, shift = 0, 0
+ for c in segment:
+ val = B64[c]
+ # Each character is 6 bits:
+ # 5 of value and the high bit is the continuation.
+ val, cont = val & 0b11111, val >> 5
+ cur += val << shift
+ shift += 5
+
+ if not cont:
+ # The low bit of the unpacked value is the sign.
+ cur, sign = cur >> 1, cur & 1
+ if sign:
+ cur = -cur
+ values.append(cur)
+ cur, shift = 0, 0
+
+ if cur or shift:
+ raise Exception('leftover cur/shift in vlq decode')
+
+ return values
+
+
+def parse_sourcemap(sourcemap):
+ """
+ Given a file-like object, yield SourceMap objects as they are read from it.
+ """
+
+ smap = json.loads(sourcemap)
+ sources = smap['sources']
+ names = smap['names']
+ mappings = smap['mappings']
+ lines = mappings.split(';')
+
+ dst_col, src_id, src_line, src_col, name_id = 0, 0, 0, 0, 0
+ for dst_line, line in enumerate(lines):
+ segments = line.split(',')
+ dst_col = 0
+ for segment in segments:
+ if not segment:
+ continue
+ parse = parse_vlq(segment)
+ dst_col += parse[0]
+
+ src = None
+ name = None
+ if len(parse) > 1:
+ src_id += parse[1]
+ src = sources[src_id]
+ src_line += parse[2]
+ src_col += parse[3]
+
+ if len(parse) > 4:
+ name_id += parse[4]
+ name = names[name_id]
+
+ assert dst_line >= 0
+ assert dst_col >= 0
+ assert src_line >= 0
+ assert src_col >= 0
+
+ yield SourceMap(dst_line, dst_col, src, src_line, src_col, name)
+
+
+def sourcemap_to_index(parsed_sourcemap):
+ state_list = []
+ key_list = []
+
+ for state in parsed_sourcemap:
+ state_list.append(state)
+ key_list.append((state.dst_line, state.dst_col))
+
+ return SourceMapIndex(state_list, key_list)
+
+
+def find_source(indexed_sourcemap, lineno, colno):
+ # error says "line no 0, column no 56"
+ return indexed_sourcemap.state_list[bisect.bisect_left(indexed_sourcemap.key_list, (0, 56)) - 1]
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+from sentry.utils.sourcemaps import (SourceMap, parse_vlq, parse_sourcemap, sourcemap_to_index,
+ find_source)
+from sentry.testutils import TestCase
+
+
+sourcemap = """{"version":3,"file":"file.min.js","sources":["file1.js","file2.js"],"names":["add","a","b","multiply","divide","c","e","Raven","captureException"],"mappings":"AAAA,QAASA,KAAIC,EAAGC,GACf,YACA,OAAOD,GAAIC,ECFZ,QAASC,UAASF,EAAGC,GACpB,YACA,OAAOD,GAAIC,EAEZ,QAASE,QAAOH,EAAGC,GAClB,YACA,KACC,MAAOC,UAASH,IAAIC,EAAGC,GAAID,EAAGC,GAAKG,EAClC,MAAOC,GACRC,MAAMC,iBAAiBF"}"""
+
+
+class ParseVlqTest(TestCase):
+ def test_simple(self):
+ assert parse_vlq('gqjG') == [100000]
+ assert parse_vlq('hqjG') == [-100000]
+ assert parse_vlq('DFLx+BhqjG') == [-1, -2, -5, -1000, -100000]
+ assert parse_vlq('CEKw+BgqjG') == [1, 2, 5, 1000, 100000]
+ assert parse_vlq('/+Z') == [-13295]
+
+
+class FindSourceTest(TestCase):
+ def test_simple(self):
+ parsed_sourcemap = parse_sourcemap(sourcemap)
+ indexed_sourcemap = sourcemap_to_index(parsed_sourcemap)
+
+ result = find_source(indexed_sourcemap, 0, 56)
+
+ assert result == SourceMap(dst_line=0, dst_col=50, src='file2.js', src_line=0, src_col=9, name='multiply')
+
+
+class ParseSourcemapTest(TestCase):
+ states = list(parse_sourcemap(sourcemap))
+
+ assert states == [
+ SourceMap(dst_line=0, dst_col=0, src='file1.js', src_line=0, src_col=0, name=None),
+ SourceMap(dst_line=0, dst_col=8, src='file1.js', src_line=0, src_col=9, name='add'),
+ SourceMap(dst_line=0, dst_col=13, src='file1.js', src_line=0, src_col=13, name='a'),
+ SourceMap(dst_line=0, dst_col=15, src='file1.js', src_line=0, src_col=16, name='b'),
+ SourceMap(dst_line=0, dst_col=18, src='file1.js', src_line=1, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=30, src='file1.js', src_line=2, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=37, src='file1.js', src_line=2, src_col=8, name='a'),
+ SourceMap(dst_line=0, dst_col=40, src='file1.js', src_line=2, src_col=12, name='b'),
+ SourceMap(dst_line=0, dst_col=42, src='file2.js', src_line=0, src_col=0, name=None),
+ SourceMap(dst_line=0, dst_col=50, src='file2.js', src_line=0, src_col=9, name='multiply'),
+ SourceMap(dst_line=0, dst_col=60, src='file2.js', src_line=0, src_col=18, name='a'),
+ SourceMap(dst_line=0, dst_col=62, src='file2.js', src_line=0, src_col=21, name='b'),
+ SourceMap(dst_line=0, dst_col=65, src='file2.js', src_line=1, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=77, src='file2.js', src_line=2, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=84, src='file2.js', src_line=2, src_col=8, name='a'),
+ SourceMap(dst_line=0, dst_col=87, src='file2.js', src_line=2, src_col=12, name='b'),
+ SourceMap(dst_line=0, dst_col=89, src='file2.js', src_line=4, src_col=0, name=None),
+ SourceMap(dst_line=0, dst_col=97, src='file2.js', src_line=4, src_col=9, name='divide'),
+ SourceMap(dst_line=0, dst_col=105, src='file2.js', src_line=4, src_col=16, name='a'),
+ SourceMap(dst_line=0, dst_col=107, src='file2.js', src_line=4, src_col=19, name='b'),
+ SourceMap(dst_line=0, dst_col=110, src='file2.js', src_line=5, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=122, src='file2.js', src_line=6, src_col=1, name=None),
+ SourceMap(dst_line=0, dst_col=127, src='file2.js', src_line=7, src_col=2, name=None),
+ SourceMap(dst_line=0, dst_col=133, src='file2.js', src_line=7, src_col=9, name='multiply'),
+ SourceMap(dst_line=0, dst_col=143, src='file2.js', src_line=7, src_col=18, name='add'),
+ SourceMap(dst_line=0, dst_col=147, src='file2.js', src_line=7, src_col=22, name='a'),
+ SourceMap(dst_line=0, dst_col=149, src='file2.js', src_line=7, src_col=25, name='b'),
+ SourceMap(dst_line=0, dst_col=152, src='file2.js', src_line=7, src_col=29, name='a'),
+ SourceMap(dst_line=0, dst_col=154, src='file2.js', src_line=7, src_col=32, name='b'),
+ SourceMap(dst_line=0, dst_col=157, src='file2.js', src_line=7, src_col=37, name='c'),
+ SourceMap(dst_line=0, dst_col=159, src='file2.js', src_line=8, src_col=3, name=None),
+ SourceMap(dst_line=0, dst_col=165, src='file2.js', src_line=8, src_col=10, name='e'),
+ SourceMap(dst_line=0, dst_col=168, src='file2.js', src_line=9, src_col=2, name='Raven'),
+ SourceMap(dst_line=0, dst_col=174, src='file2.js', src_line=9, src_col=8, name='captureException'),
+ SourceMap(dst_line=0, dst_col=191, src='file2.js', src_line=9, src_col=25, name='e'),
+ ]

0 comments on commit 7d29641

Please sign in to comment.