|
1 |
| -(function (root, factory) { |
| 1 | +(function(root, factory) { |
2 | 2 | 'use strict';
|
3 | 3 | // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
|
4 | 4 |
|
|
10 | 10 | } else {
|
11 | 11 | root.StackTraceGPS = factory(root.SourceMap || root.sourceMap, root.StackFrame);
|
12 | 12 | }
|
13 |
| -}(this, function (SourceMap, StackFrame) { |
| 13 | +}(this, function(SourceMap, StackFrame) { |
14 | 14 | 'use strict';
|
15 | 15 |
|
16 | 16 | /**
|
17 | 17 | * Make a X-Domain request to url and callback.
|
18 | 18 | *
|
19 |
| - * @param url [String] |
20 |
| - * @return Promise with response text if fulfilled |
| 19 | + * @param {String} url |
| 20 | + * @returns {Promise} with response text if fulfilled |
21 | 21 | */
|
22 | 22 | function _xdr(url) {
|
23 |
| - return new Promise(function (resolve, reject) { |
| 23 | + return new Promise(function(resolve, reject) { |
24 | 24 | var req = new XMLHttpRequest();
|
25 | 25 | req.open('get', url);
|
26 | 26 | req.onerror = reject;
|
|
42 | 42 | * Convert a Base64-encoded string into its original representation.
|
43 | 43 | * Used for inline sourcemaps.
|
44 | 44 | *
|
45 |
| - * @param b64str [String] |
46 |
| - * @return The original representation of the base64-encoded string. |
| 45 | + * @param {String} b64str Base-64 encoded string |
| 46 | + * @returns {String} original representation of the base64-encoded string. |
47 | 47 | */
|
48 | 48 | function _atob(b64str) {
|
49 | 49 | if (typeof window !== 'undefined' && window.atob) {
|
|
53 | 53 | }
|
54 | 54 | }
|
55 | 55 |
|
56 |
| - function _findFunctionName(source, lineNumber, columnNumber) { |
| 56 | + function _parseJson(string) { |
| 57 | + if (typeof JSON !== 'undefined' && JSON.parse) { |
| 58 | + return JSON.parse(string); |
| 59 | + } else { |
| 60 | + throw new Error('You must supply a polyfill for JSON.parse in this environment'); |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + function _findFunctionName(source, lineNumber/*, columnNumber*/) { |
57 | 65 | // function {name}({args}) m[1]=name m[2]=args
|
58 | 66 | var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
|
59 | 67 | // {name} = function ({args}) TODO args capture
|
|
63 | 71 | var lines = source.split('\n');
|
64 | 72 |
|
65 | 73 | // Walk backwards in the source lines until we find the line which matches one of the patterns above
|
66 |
| - var code = '', line, maxLines = Math.min(lineNumber, 20), m, commentPos; |
| 74 | + var code = ''; |
| 75 | + var maxLines = Math.min(lineNumber, 20); |
| 76 | + var m; |
67 | 77 | for (var i = 0; i < maxLines; ++i) {
|
68 | 78 | // lineNo is 1-based, source[] is 0-based
|
69 |
| - line = lines[lineNumber - i - 1]; |
70 |
| - commentPos = line.indexOf('//'); |
| 79 | + var line = lines[lineNumber - i - 1]; |
| 80 | + var commentPos = line.indexOf('//'); |
71 | 81 | if (commentPos >= 0) {
|
72 | 82 | line = line.substr(0, commentPos);
|
73 | 83 | }
|
|
102 | 112 | throw new TypeError('Given StackFrame is not an object');
|
103 | 113 | } else if (typeof stackframe.fileName !== 'string') {
|
104 | 114 | throw new TypeError('Given file name is not a String');
|
105 |
| - } else if (typeof stackframe.lineNumber !== 'number' || stackframe.lineNumber % 1 !== 0 || stackframe.lineNumber < 1) { |
| 115 | + } else if (typeof stackframe.lineNumber !== 'number' || |
| 116 | + stackframe.lineNumber % 1 !== 0 || |
| 117 | + stackframe.lineNumber < 1) { |
106 | 118 | throw new TypeError('Given line number must be a positive integer');
|
107 |
| - } else if (typeof stackframe.columnNumber !== 'number' || stackframe.columnNumber % 1 !== 0 || stackframe.columnNumber < 0) { |
| 119 | + } else if (typeof stackframe.columnNumber !== 'number' || |
| 120 | + stackframe.columnNumber % 1 !== 0 || |
| 121 | + stackframe.columnNumber < 0) { |
108 | 122 | throw new TypeError('Given column number must be a non-negative integer');
|
109 | 123 | }
|
110 | 124 | return true;
|
|
136 | 150 | }
|
137 | 151 |
|
138 | 152 | /**
|
139 |
| - * @param opts: [Object] options. |
| 153 | + * @constructor |
| 154 | + * @param {Object} opts |
140 | 155 | * opts.sourceCache = {url: "Source String"} => preload source cache
|
141 | 156 | * opts.offline = True to prevent network requests.
|
142 | 157 | * Best effort without sources or source maps.
|
|
155 | 170 | this._atob = opts.atob || _atob;
|
156 | 171 |
|
157 | 172 | this._get = function _get(location) {
|
158 |
| - return new Promise(function (resolve, reject) { |
| 173 | + return new Promise(function(resolve, reject) { |
159 | 174 | var isDataUrl = location.substr(0, 5) === 'data:';
|
160 | 175 | if (this.sourceCache[location]) {
|
161 | 176 | resolve(this.sourceCache[location]);
|
|
191 | 206 | * Given a StackFrame, enhance function name and use source maps for a
|
192 | 207 | * better StackFrame.
|
193 | 208 | *
|
194 |
| - * @param stackframe - {StackFrame}-like object |
195 |
| - * {fileName: 'path/to/file.js', lineNumber: 100, columnNumber: 5} |
196 |
| - * @return StackFrame with source-mapped location |
| 209 | + * @param {StackFrame} stackframe object |
| 210 | + * @returns {Promise} that resolves with with source-mapped StackFrame |
197 | 211 | */
|
198 | 212 | this.pinpoint = function StackTraceGPS$$pinpoint(stackframe) {
|
199 |
| - return new Promise(function (resolve, reject) { |
200 |
| - this.getMappedLocation(stackframe).then(function (mappedStackFrame) { |
| 213 | + return new Promise(function(resolve, reject) { |
| 214 | + this.getMappedLocation(stackframe).then(function(mappedStackFrame) { |
201 | 215 | function resolveMappedStackFrame() {
|
202 | 216 | resolve(mappedStackFrame);
|
203 | 217 | }
|
|
212 | 226 | /**
|
213 | 227 | * Given a StackFrame, guess function name from location information.
|
214 | 228 | *
|
215 |
| - * @param stackframe - {StackFrame}-like object |
216 |
| - * {fileName: 'path/to/file.js', lineNumber: 100, columnNumber: 5} |
217 |
| - * @return StackFrame with guessed function name |
| 229 | + * @param {StackFrame} stackframe |
| 230 | + * @returns {Promise} that resolves with enhanced StackFrame. |
218 | 231 | */
|
219 | 232 | this.findFunctionName = function StackTraceGPS$$findFunctionName(stackframe) {
|
220 |
| - return new Promise(function (resolve, reject) { |
| 233 | + return new Promise(function(resolve, reject) { |
221 | 234 | _ensureStackFrameIsLegit(stackframe);
|
222 | 235 | this._get(stackframe.fileName).then(function getSourceCallback(source) {
|
223 | 236 | var guessedFunctionName = _findFunctionName(source, stackframe.lineNumber, stackframe.columnNumber);
|
224 |
| - resolve(new StackFrame(guessedFunctionName, stackframe.args, stackframe.fileName, stackframe.lineNumber, stackframe.columnNumber)); |
| 237 | + resolve(new StackFrame(guessedFunctionName, |
| 238 | + stackframe.args, |
| 239 | + stackframe.fileName, |
| 240 | + stackframe.lineNumber, |
| 241 | + stackframe.columnNumber)); |
225 | 242 | }, reject)['catch'](reject);
|
226 | 243 | }.bind(this));
|
227 | 244 | };
|
228 | 245 |
|
229 | 246 | /**
|
230 | 247 | * Given a StackFrame, seek source-mapped location and return new enhanced StackFrame.
|
231 | 248 | *
|
232 |
| - * @param stackframe - {StackFrame}-like object |
233 |
| - * {fileName: 'path/to/file.js', lineNumber: 100, columnNumber: 5} |
234 |
| - * @return StackFrame with source-mapped location |
| 249 | + * @param {StackFrame} stackframe |
| 250 | + * @returns {Promise} that resolves with enhanced StackFrame. |
235 | 251 | */
|
236 | 252 | this.getMappedLocation = function StackTraceGPS$$getMappedLocation(stackframe) {
|
237 |
| - return new Promise(function (resolve, reject) { |
| 253 | + return new Promise(function(resolve, reject) { |
238 | 254 | _ensureSupportedEnvironment();
|
239 | 255 | _ensureStackFrameIsLegit(stackframe);
|
240 | 256 |
|
241 | 257 | var sourceCache = this.sourceCache;
|
242 | 258 | var fileName = stackframe.fileName;
|
243 |
| - this._get(fileName).then(function (source) { |
| 259 | + this._get(fileName).then(function(source) { |
244 | 260 | var sourceMappingURL = _findSourceMappingURL(source);
|
245 | 261 | var isDataUrl = sourceMappingURL.substr(0, 5) === 'data:';
|
| 262 | + var base = fileName.substring(0, fileName.lastIndexOf('/') + 1); |
246 | 263 |
|
247 | 264 | if (sourceMappingURL[0] !== '/' && !isDataUrl && !(/^https?:\/\/|^\/\//i).test(sourceMappingURL)) {
|
248 |
| - sourceMappingURL = fileName.substring(0, fileName.lastIndexOf('/') + 1) + sourceMappingURL; |
| 265 | + sourceMappingURL = base + sourceMappingURL; |
249 | 266 | }
|
250 | 267 |
|
251 |
| - this._get(sourceMappingURL).then(function (map) { |
252 |
| - var lineNumber = stackframe.lineNumber; |
253 |
| - var columnNumber = stackframe.columnNumber; |
254 |
| - resolve(_extractLocationInfoFromSourceMap(map, stackframe.args, lineNumber, columnNumber, sourceCache)); |
| 268 | + this._get(sourceMappingURL).then(function(map) { |
| 269 | + var line = stackframe.lineNumber; |
| 270 | + var column = stackframe.columnNumber; |
| 271 | + |
| 272 | + if (typeof map === 'string') { |
| 273 | + map = _parseJson(map.replace(/^\)\]\}'/, '')); |
| 274 | + } |
| 275 | + |
| 276 | + if (typeof map.sourceRoot === 'undefined') { |
| 277 | + map.sourceRoot = base; |
| 278 | + } |
| 279 | + |
| 280 | + resolve(_extractLocationInfoFromSourceMap(map, stackframe.args, line, column, sourceCache)); |
255 | 281 | }, reject)['catch'](reject);
|
256 | 282 | }.bind(this), reject)['catch'](reject);
|
257 | 283 | }.bind(this));
|
|
0 commit comments