Repository: getsentry/sentry

Issue:
Sentry should respect sourceMappingURL at EOL
Right now Sentry requires the `sourceMappingURL` directive to be on its own line at the end of a file, which is the literal interpretation of the spec.

But, Chrome and Firefox are more liberal, and will respect `sourceMappingURL` if it follows a bunch of code on the last line of the file, e.g.

```javascript
function add(){for(var a=arguments.length,b=Array(a),c=0;c<a;c++)b[c]=arguments[c];return b.reduce(function(d,e){return d+=e,d},0)}//# sourceMappingURL=app.min.js.map
```

As pointed out by a user, this is easier to produce than first thought:

> However, Webpack's SourceMapDevToolPlugin has an 'append' option, and this requires the dev to remember to add a new line before their sourceMappingURL comment. This is the part that I missed.

Source maps are hard enough as it is; we should probably make this change to be more flexible and make integration easier on users.

cc @maxbittker @mattrobenolt 


Discussion:

Resolving patch:
diff --git a/src/sentry/lang/javascript/processor.py b/src/sentry/lang/javascript/processor.py
--- a/src/sentry/lang/javascript/processor.py
+++ b/src/sentry/lang/javascript/processor.py
@@ -52,7 +52,7 @@ class ZeroReturnError(Exception):
 """, re.X | re.I)
 VERSION_RE = re.compile(r'^[a-f0-9]{32}|[a-f0-9]{40}$', re.I)
 NODE_MODULES_RE = re.compile(r'\bnode_modules/')
-
+SOURCE_MAPPING_URL_RE = re.compile(r'\/\/# sourceMappingURL=(.*)$')
 # the maximum number of remote resources (i.e. sourc eifles) that should be
 # fetched
 MAX_RESOURCE_FETCHES = 100
@@ -155,6 +155,19 @@ def discover_sourcemap(result):
                 # We want everything AFTER the indicator, which is 21 chars long
                 sourcemap = line[21:].rstrip()
 
+        # If we still haven't found anything, check end of last line AFTER source code.
+        # This is not the literal interpretation of the spec, but browsers support it.
+        # e.g. {code}//# sourceMappingURL={url}
+        if not sourcemap:
+            # Only look at last 300 characters to keep search space reasonable (minified
+            # JS on a single line could be tens of thousands of chars). This is a totally
+            # arbitrary number / best guess; most sourceMappingURLs are relative and
+            # not very long.
+            search_space = possibilities[-1][-300:].rstrip()
+            match = SOURCE_MAPPING_URL_RE.search(search_space)
+            if match:
+                sourcemap = match.group(1)
+
     if sourcemap:
         # react-native shoves a comment at the end of the
         # sourceMappingURL line.


Test patch:
diff --git a/tests/sentry/lang/javascript/test_processor.py b/tests/sentry/lang/javascript/test_processor.py
--- a/tests/sentry/lang/javascript/test_processor.py
+++ b/tests/sentry/lang/javascript/test_processor.py
@@ -216,6 +216,10 @@ def test_simple(self):
         result = http.UrlResult('http://example.com', {}, 'console.log(true)\n//# sourceMappingURL=http://example.com/source.map.js\n//# sourceMappingURL=http://example.com/source2.map.js', 200, None)
         assert discover_sourcemap(result) == 'http://example.com/source2.map.js'
 
+        # sourceMappingURL found directly after code w/o newline
+        result = http.UrlResult('http://example.com', {}, 'console.log(true);//# sourceMappingURL=http://example.com/source.map.js', 200, None)
+        assert discover_sourcemap(result) == 'http://example.com/source.map.js'
+
         result = http.UrlResult('http://example.com', {}, '//# sourceMappingURL=app.map.js/*ascii:lol*/', 200, None)
         assert discover_sourcemap(result) == 'http://example.com/app.map.js'