Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getContext feature implementation (similar to TraceKit context field) #64

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ https://raw.githubusercontent.com/stacktracejs/stacktrace-gps/master/dist/stackt
options: Object
* **sourceCache: Object (String URL : String Source)** - Pre-populate source cache to avoid network requests
* **sourceMapConsumerCache: Object (Source Mapping URL : SourceMap.SourceMapConsumer)** - Pre-populate source cache to avoid network requests
* **sourceCache: Object (String URL : String Source)** - Pre-populate source cache to avoid network requests
* **sourceMapConsumerCache: Object (Source Mapping URL : SourceMap.SourceMapConsumer)** - Pre-populate source cache to avoid network requests
* **offline: Boolean (default false)** - Set to `true` to prevent all network requests
* **contextMaxLineLength: Number (default 200)** - The maximum length of one line in the context source code
* **contextMaxLinesCount: Number (default 5)** - The maximum number of lines in the context source code
* **offline: Boolean (default false)** - Set to `true` to prevent all network requests
* **ajax: Function (String URL => Promise(responseText))** - Function to be used for making X-Domain requests
* **atob: Function (String => String)** - Function to convert base64-encoded strings to their original representation
Expand All @@ -79,6 +84,10 @@ Enhance function name and use source maps to produce a better StackFrame.
Enhance function name and use source maps to produce a better StackFrame.
* **stackframe** - [StackFrame](https://github.com/stacktracejs/stackframe) or like object

#### `.getContext(stackframe)` => Promise({ source, lineNumber, columnNumber })
Returns the surrounding source code chunk (with the start position) for where the stackframe points.
* **stackframe** - [StackFrame](https://github.com/stacktracejs/stackframe) or like object

## Browser Support
[![Sauce Test Status](https://saucelabs.com/browser-matrix/stacktracejs.svg)](https://saucelabs.com/u/stacktracejs)

Expand Down
70 changes: 70 additions & 0 deletions spec/stacktrace-gps-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,74 @@ describe('StackTraceGPS', function() {
}
});
});

describe('#_getContextSingleLineIndexes', function() {
it('returns indexes that determine the correct range', function(done) {
test(1);
test(4);
test(5);
done();

function test(contextMaxLineLength) {
var stackTraceGPS = new StackTraceGPS({ contextMaxLineLength: contextMaxLineLength });
var line = '';
for (var length = 1; length <= 10; length++) {
line += 'x';
for (var columnNumber = 1; columnNumber <= 1; columnNumber++) {
var indexes = stackTraceGPS._getContextSingleLineIndexes(line, columnNumber);
expect(indexes[1] - indexes[0]).toBe(Math.min(contextMaxLineLength, length));
expect(indexes[1] >= columnNumber && columnNumber > indexes[0]).toBe(true);
}
}
}
});
});

describe('#_getContextMultipleLinesIndexes', function() {
it('returns indexes that determine the correct range', function(done) {
test(1);
test(4);
test(5);
done();

function test(contextMaxLinesCount) {
var stackTraceGPS = new StackTraceGPS({ contextMaxLinesCount: contextMaxLinesCount });
var lines = [];
for (var count = 1; count <= 10; count++) {
lines.push('x');
for (var columnNumber = 1; columnNumber <= 1; columnNumber++) {
var indexes = stackTraceGPS._getContextMultipleLinesIndexes(lines, columnNumber);
expect(indexes[1] - indexes[0]).toBe(Math.min(contextMaxLinesCount, count));
expect(indexes[1] >= columnNumber && columnNumber > indexes[0]).toBe(true);
}
}
}
});

it('avoids too long lines', function(done) {
var lines = [
'line',
'too long line',
'line',
'line',
'line',
'too long line',
'line'
];
var stackTraceGPS = new StackTraceGPS({ contextMaxLineLength: 4, contextMaxLinesCount: 2 });
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 1)).toEqual([0, 1]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 3)).toEqual([2, 4]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 4)).toEqual([2, 4]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 5)).toEqual([3, 5]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 7)).toEqual([6, 7]);
stackTraceGPS.contextMaxLinesCount = 5;
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 1)).toEqual([0, 1]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 3)).toEqual([2, 5]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 4)).toEqual([2, 5]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 5)).toEqual([2, 5]);
expect(stackTraceGPS._getContextMultipleLinesIndexes(lines, 7)).toEqual([6, 7]);
done();
});
});

});
98 changes: 98 additions & 0 deletions stacktrace-gps.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@
* @param {Object} opts
* opts.sourceCache = {url: "Source String"} => preload source cache
* opts.sourceMapConsumerCache = {/path/file.js.map: SourceMapConsumer}
* opts.contextMaxLineLength = {Number}
* opts.contextMaxLinesCount = {Number}
* opts.offline = True to prevent network requests.
* Best effort without sources or source maps.
* opts.ajax = Promise returning function to make X-Domain requests
Expand All @@ -187,6 +189,9 @@
this.sourceCache = opts.sourceCache || {};
this.sourceMapConsumerCache = opts.sourceMapConsumerCache || {};

this.contextMaxLineLength = opts.contextMaxLineLength || 200;
this.contextMaxLinesCount = opts.contextMaxLinesCount || 5;

this.ajax = opts.ajax || _xdr;

this._atob = opts.atob || _atob;
Expand Down Expand Up @@ -338,5 +343,98 @@
}.bind(this), reject)['catch'](reject);
}.bind(this));
};

/**
* Given the source code line and the column number,
* truncate the line to contextMaxLineLength,
* return the start and end indexes of the truncated line.
*
* @param {String} line
* @param {Number} columnNumber
* @returns {Object}
*/
this._getContextSingleLineIndexes = function _getContextSingleLineIndexes(line, columnNumber) {
var indexStart = columnNumber - 1 - Math.floor(this.contextMaxLineLength / 2);
if (indexStart < 0) {
indexStart = 0;
}
var indexEnd = indexStart + this.contextMaxLineLength;
if (indexEnd > line.length) {
indexEnd = line.length;
indexStart = indexEnd - this.contextMaxLineLength;
if (indexStart < 0) {
indexStart = 0;
}
}
return [indexStart, indexEnd];
};

/**
* Given the source code lines and the line number,
* truncate the lines array to contextMaxLinesCount,
* avoid the lines with length more than contextMaxLineLength,
* return the object with the lines subarray and its start line number.
*
* @param {Array.String} lines
* @param {Number} lineNumber
* @returns {Object}
*/
this._getContextMultipleLinesIndexes = function _getContextMultipleLinesIndexes(lines, lineNumber) {
var indexes = [lineNumber - 1, lineNumber];
var active = [true, true];
var step = 0;
while (indexes[1] - indexes[0] < this.contextMaxLinesCount) {
var line = lines[indexes[step] + step - 1];
if (line === undefined || line.length > this.contextMaxLineLength) {
active[step] = false;
} else {
indexes[step] += 2 * step - 1;
}
if (active[1 - step]) {
step = 1 - step;
} else if (!active[step]) {
break;
}
}
return indexes;
};

/**
* Given a StackFrame, return a surrounding source code part with its start position.
*
* @param {StackFrame} stackframe
* @returns {Promise}
*/
this.getContext = function StackTraceGPS$$getContext(stackframe) {
return new Promise(function(resolve, reject) {
_ensureSupportedEnvironment();
_ensureStackFrameIsLegit(stackframe);

var fileName = stackframe.fileName;
var lineNumber = stackframe.lineNumber;
var columnNumber = stackframe.columnNumber;
this._get(fileName).then(function(source) {
var lines = source.split('\n');
var line = lines[lineNumber - 1];
var indexes, context;
if (line.length > this.contextMaxLineLength) {
indexes = this._getContextSingleLineIndexes(line, columnNumber);
context = {
source: line.substring(indexes[0], indexes[1]),
lineNumber: lineNumber,
columnNumber: indexes[0] + 1
};
} else {
indexes = this._getContextMultipleLinesIndexes(lines, lineNumber);
context = {
source: lines.slice(indexes[0], indexes[1]).join('\n'),
lineNumber: indexes[0] + 1,
columnNumber: 0
};
}
resolve(context);
}.bind(this), reject)['catch'](reject);
}.bind(this));
};
};
}));