Skip to content

Commit

Permalink
Get rid of "retainLines" compiler option (closes DevExpress#1267)
Browse files Browse the repository at this point in the history
  • Loading branch information
Georgiy Abbasov committed Mar 29, 2017
1 parent 21321c4 commit 156ba8f
Show file tree
Hide file tree
Showing 9 changed files with 465 additions and 8 deletions.
306 changes: 306 additions & 0 deletions modules/callsite-record/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
var fs = require('fs');
var Promise = require('pinkie-promise');
var callsite = require('callsite');
var stackParser = require('error-stack-parser');
var padStart = require('lodash').padStart;
var defaults = require('lodash').defaults;
var highlight = require('highlight-es');
var wrapCallSite = require('source-map-support').wrapCallSite;

var renderers = {
default: require('./renderers/default'),
noColor: require('./renderers/no-color'),
html: require('./renderers/html')
};


var NEWLINE = /\r\n|[\n\r\u2028\u2029]/;

// Utils
function parseStack (error) {
try {
return stackParser.parse(error);
}
catch (err) {
return null;
}
}

function getFrameTypeName (frame) {
// NOTE: this throws in node 10 for non-methods
try {
return frame.getTypeName();
}
catch (err) {
return null;
}
}

function findClosestNonNativeAncestorFrameIdx (stackFrames, curIdx) {
for (var i = curIdx + 1; i < stackFrames.length; i++) {
if (!stackFrames[i].isNative())
return i;
}

return null;
}

function isV8StackFrame (frame) {
return /CallSite/.test(frame.constructor);
}

function getFrameMethodName (frame, funcName) {
// NOTE: Code was partially adopted from the V8 code
// (see: https://github.com/v8/v8/blob/3c3d7e7be80f45eeea0dc74a71d7552e2afc2985/src/js/messages.js#L647)
var typeName = frame.getTypeName();
var methodName = frame.getMethodName();

if (funcName) {
var name = '';
var funcNameStartsWithTypeName = typeName && funcName.indexOf(typeName) === 0;
var funcNameEndsWithMethodName = methodName &&
funcName.indexOf('.' + methodName) === funcName.length - methodName.length - 1;

if (!funcNameStartsWithTypeName)
name = typeName + '.';

name += funcName;

if (!funcNameEndsWithMethodName)
name += ' [as ' + methodName + ']';

return name;
}

return typeName + '.' + (methodName || '<anonymous>');
}


// CallsiteRecord
var CallsiteRecord = function (filename, lineNum, callsiteFrameIdx, stackFrames) {
this.filename = filename;
this.lineNum = lineNum;
this.callsiteFrameIdx = callsiteFrameIdx;
this.stackFrames = stackFrames;
this.isV8Frames = isV8StackFrame(this.stackFrames[0]);
};

CallsiteRecord.prototype._getFrameName = function (frame) {
// NOTE: Code was partially adopted from the V8 code
// (see: https://github.com/v8/v8/blob/3c3d7e7be80f45eeea0dc74a71d7552e2afc2985/src/js/messages.js#L647)
var funcName = frame.getFunctionName();

if (!this.isV8Frames)
return funcName || '<anonymous>';

var isCtor = frame.isConstructor();
var isMethod = !frame.isToplevel() && !isCtor;

if (isMethod)
return getFrameMethodName(frame, funcName);

funcName = funcName || '<anonymous>';

return isCtor ? 'new ' + funcName : funcName;
};

CallsiteRecord.prototype._getFrameLocation = function (frame) {
// NOTE: Code was partially adopted from the V8 code
// (see: https://github.com/v8/v8/blob/3c3d7e7be80f45eeea0dc74a71d7552e2afc2985/src/js/messages.js#L647)
if (this.isV8Frames && frame.isNative())
return 'native';

var location = frame.getFileName();
var lineNum = frame.getLineNumber();
var colNum = frame.getColumnNumber();

if (this.isV8Frames && !location) {
location = frame.isEval() ? frame.getEvalOrigin() + ', ' : '';
location += '<anonymous>';
}

if (lineNum) {
location += ':' + lineNum;

if (colNum)
location += ':' + colNum;
}

return location;
};

CallsiteRecord.prototype._getCodeFrameLines = function (fileContent, frameSize) {
var lines = fileContent.split(NEWLINE);
var startLineIdx = Math.max(0, this.lineNum - frameSize);
var endLineIdx = Math.min(lines.length - 1, this.lineNum + frameSize);
var maxLineNumDigits = 0;
var frameLines = [];

for (var i = startLineIdx; i <= endLineIdx; i++) {
var num = String(i + 1);

maxLineNumDigits = Math.max(maxLineNumDigits, num.length);

frameLines.push({
num: num,
src: lines[i],
base: i === this.lineNum
});
}

frameLines.forEach(function (line) {
line.num = padStart(line.num, maxLineNumDigits);
});

return frameLines;
};

CallsiteRecord.prototype._renderCodeFrame = function (fileContent, renderer, frameSize) {
if (renderer.syntax)
fileContent = highlight(fileContent, renderer.syntax);

var lines = this._getCodeFrameLines(fileContent, frameSize);
var lastIdx = lines.length - 1;

var frame = lines
.reduce(function (sourceFrame, line, idx) {
var isLast = idx === lastIdx;

return sourceFrame + renderer.codeLine(line.num, line.base, line.src, isLast);
}, '');

return renderer.codeFrame(frame);
};


CallsiteRecord.prototype._renderStack = function (renderer, stackFilter) {
var record = this;
var entries = this.stackFrames.slice(this.callsiteFrameIdx);

if (stackFilter) {
entries = entries.filter(function (frame, idx) {
return stackFilter(frame, idx, record.isV8Frames);
});
}

var lastIdx = entries.length - 1;

var rendered = entries.reduce(function (str, frame, idx) {
var isLast = idx === lastIdx;
var name = record._getFrameName(frame);
var location = record._getFrameLocation(frame);

return str + renderer.stackLine(name, location, isLast);
}, '');

return rendered ? renderer.stack(rendered) : '';
};

CallsiteRecord.prototype._renderRecord = function (fileContent, opts) {
opts = defaults({}, opts, {
renderer: renderers.default,
frameSize: 5,
stack: true,
codeFrame: true,
stackFilter: null
}, opts);

var codeFrame = opts.codeFrame ? this._renderCodeFrame(fileContent, opts.renderer, opts.frameSize) : '';
var stack = opts.stack ? this._renderStack(opts.renderer, opts.stackFilter) : '';

return codeFrame + stack;
};

CallsiteRecord.prototype.renderSync = function (opts) {
var fileContent = fs.readFileSync(this.filename).toString();

return this._renderRecord(fileContent, opts);
};

CallsiteRecord.prototype.render = function (opts) {
var record = this;

return new Promise(function (resolve, reject) {
fs.readFile(record.filename, function (err, fileContent) {
if (err)
reject(err);
else
resolve(record._renderRecord(fileContent.toString(), opts));
});
});
};

// Static
CallsiteRecord.fromStackFrames = function (stackFrames, fnName, typeName) {
if (typeName && fnName === 'constructor')
fnName = typeName;

for (var i = 0; i < stackFrames.length; i++) {
var frame = stackFrames[i];
var fnNameMatch = frame.getFunctionName() === fnName || frame.getMethodName() === fnName;
var typeNameMatch = !typeName || getFrameTypeName(frame) === typeName;

if (fnNameMatch && typeNameMatch) {
var callsiteFrameIdx = findClosestNonNativeAncestorFrameIdx(stackFrames, i);

if (callsiteFrameIdx !== null) {
var callsiteFrame = stackFrames[callsiteFrameIdx];
var filename = callsiteFrame.getFileName();
var lineNum = callsiteFrame.getLineNumber() - 1;

return new CallsiteRecord(filename, lineNum, callsiteFrameIdx, stackFrames);
}

return null;
}
}

return null;
};

CallsiteRecord.fromError = function (error, isCallsiteFrame) {
var stackFrames = parseStack(error);

if (stackFrames) {
if (typeof isCallsiteFrame === 'function') {
while (stackFrames.length) {
if (isCallsiteFrame(stackFrames[0]))
break;

stackFrames.shift();
}
}

if (stackFrames.length) {
var filename = stackFrames[0].getFileName();
var lineNum = stackFrames[0].getLineNumber() - 1;

return filename && !isNaN(lineNum) ? new CallsiteRecord(filename, lineNum, 0, stackFrames) : null;
}
}

return null;
};

// API
module.exports = function createCallsiteRecord (/* err, isCallsiteFrame || fnName, typeName */) {
if (arguments[0] instanceof Error)
return CallsiteRecord.fromError(arguments[0], arguments[1]);

else if (typeof arguments[0] === 'string') {
var stackFrames = callsite();

stackFrames = stackFrames.map(function (frame) {
return wrapCallSite(frame)
});

// NOTE: remove API call
stackFrames.shift();

return CallsiteRecord.fromStackFrames(stackFrames, arguments[0], arguments[1]);
}

return null;
};

module.exports.renderers = renderers;
44 changes: 44 additions & 0 deletions modules/callsite-record/renderers/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
var chalk = require('chalk');
var asIs = require('lodash').identity;

module.exports = {
syntax: {
string: chalk.green,
punctuator: chalk.grey,
keyword: chalk.cyan,
number: chalk.magenta,
regex: chalk.magenta,
comment: chalk.grey.bold,
invalid: chalk.inverse
},

codeFrame: asIs,

codeLine: function (num, base, src, isLast) {
var prefix = base ? ' > ' : ' ';
var lineNum = prefix + num + ' ';

if (base)
lineNum = chalk.bgRed(lineNum);

var line = lineNum + '|' + src;

if (!isLast)
line += '\n';

return line;
},

stackLine: function (name, location, isLast) {
var line = ' at ' + chalk.bold(name) + ' (' + chalk.grey.underline(location) + ')';

if (!isLast)
line += '\n';

return line;
},

stack: function (stack) {
return '\n\n' + stack;
}
};
38 changes: 38 additions & 0 deletions modules/callsite-record/renderers/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
var escapeHtml = require('lodash').escape;

module.exports = {
syntax: ['string', 'punctuator', 'keyword', 'number', 'regex', 'comment', 'invalid'].reduce(function (syntaxRenderer, tokenType) {
syntaxRenderer[tokenType] = function (str) {
return '<span class="syntax-' + tokenType + '">' + escapeHtml(str) + '</span>';
};

return syntaxRenderer;
}, {}),

codeFrame: function (str) {
return '<div class="code-frame">' + str + '</div>';
},

codeLine: function (num, base, src, isLast) {
var lineClass = isLast ? 'code-line-last' : 'code-line';
var numClass = base ? 'code-line-num-base' : 'code-line-num';

return '<div class="' + lineClass + '">' +
'<div class="' + numClass + '">' + num + '</div>' +
'<div class="code-line-src">' + src + '</div>' +
'</div>';
},

stackLine: function (name, location, isLast) {
var lineClass = isLast ? 'stack-line-last' : 'stack-line';

return '<div class="' + lineClass + '">' +
'<div class="stack-line-name">' + escapeHtml(name) + '</div>' +
'<div class="stack-line-location">' + escapeHtml(location) + '</div>' +
'</div>';
},

stack: function (stack) {
return '<div class="stack">' + stack + '</div>';
}
};
Loading

0 comments on commit 156ba8f

Please sign in to comment.