-
Notifications
You must be signed in to change notification settings - Fork 669
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Get rid of "retainLines" compiler option (closes #1267)
- Loading branch information
Georgiy Abbasov
committed
Mar 30, 2017
1 parent
844c2b2
commit d21548c
Showing
9 changed files
with
464 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>'; | ||
} | ||
}; |
Oops, something went wrong.