UNPKG

strong-trace

Version:

StrongTrace Node.js Tracer

158 lines (136 loc) 6.03 kB
"use strict"; module.exports = FunctionTrace var scopetracer = require("./scopetracer") var est = require("estraverse") var shouldInstrument = require("./should_instrument") var tracer = require("./core_tracer").getTracer() var helpers = require("./format_helpers") var getLeadingIndent = helpers.getLeadingIndent function FunctionTrace(config) { function mutator(content, filename) { var insertions = [] tracer.addFile(filename) var complexity = shouldInstrument.complexityStats(this) var id = tracer.addFunction(filename, this.fnName, this.parent.loc.start, complexity) if (!shouldInstrument(this, config, complexity)) { return insertions } var enterInsert = "var __concurix_traceme = __concurix_tracer.enter('" + id + "');" var exitInsert = ";if (__concurix_traceme) {__concurix_tracer.exit('" + id + "')}" // Instrument function entry var nextNode = (this.isStrict) ? this.body[1] : this.body[0] var enterInsertionPoint if (nextNode != null) { enterInsertionPoint = nextNode.range[0] } else { // Empty body (noop) // + 1 for curly brace enterInsertionPoint = this.range[0] + 1 } var preserveIndent = getLeadingIndent(content, enterInsertionPoint) insertions.push({pos: enterInsertionPoint, insertion: enterInsert + preserveIndent, pri: 0}) // Instrument exit points // Traverse this function scope and see if it contains any early exits est.traverse(this, { enter: function enter(node, parent) { // Synchronous exit points are 'return' or 'throw' if (node.type == "ReturnStatement" || node.type == "ThrowStatement") { // Beware the trailing comments... var myEnd = (node.argument && node.range[1] - node.argument.range[1] > 1) ? node.argument.range[1] + 1 : node.range[1] var actualEnd = helpers.findActualEnd(content, myEnd) if (actualEnd !== node.range[1] && /\S/.test(content.slice(actualEnd, node.range[1]))) { actualEnd = node.range[1] } var preserveIndent = getLeadingIndent(content, node.range[0]) if (config.rewrite_returns && needsRewrite(node)) { var proxy = "var __concurix_rproxy = " var toRemove = "return" if (node.type == "ThrowStatement") { toRemove = "throw" } var reply = toRemove + " __concurix_rproxy;" // Return blocks with comma operators are evil. // If we see them attempt to wrap the SequenceExpression with parens if (node.argument && node.argument.type == "SequenceExpression") { proxy = proxy + "(" var endParen = actualEnd // Semicolon check. if (content.slice(actualEnd - 1, actualEnd) === ";") { endParen-- } insertions.push({pos: endParen, insertion: ")", pri: 2}) actualEnd++ } if (parent.type != "BlockStatement") { // If not in a BlockStatement, need to put it in one. proxy = "{ " + proxy reply = reply + " }" } insertions.push({pos: node.range[0], remove: toRemove.length, insertion: proxy, pri: 1}) insertions.push({pos: actualEnd, insertion: preserveIndent + exitInsert + preserveIndent + reply, pri: 3}) } else { // TBD: some sort of special-case handling of throw? if (parent.type != "BlockStatement") { // If this is not in a BlockStatement, we need to put it in one. insertions.push({pos: node.range[0], insertion: "{ " + exitInsert + preserveIndent, pri: 1}) // Esprima will emit an overlapping range when no semicolon on inline if consequents // Thus we do some sniffing... insertions.push({pos: actualEnd, insertion: " }"}) } else { insertions.push({pos: node.range[0], insertion: exitInsert + preserveIndent, pri: 2}) } } } // Don't decend into nested functions else if (node.type == "FunctionExpression" || node.type == "FunctionDeclaration") { return est.VisitorOption.Skip } } }) // Insert at end of block if previous statment wasn't return or throw // e.g. exit at run off end of block var lastMember = this.body[this.body.length - 1] if (lastMember == null) { // Empty body (noop function?) insertions.push({pos: this.range[1] - 1, insertion: exitInsert, pri: 1}) } else if (lastMember.type != "ReturnStatement" && lastMember.type != "ThrowStatement") { preserveIndent = getLeadingIndent(content, lastMember.range[0]) if (!/\n/.test(preserveIndent) && content.slice(lastMember.range[1] - 1, lastMember.range[1]) !== ";") { // no newline or semicolon on previous statement -- add a semicolon preserveIndent = ";" + preserveIndent } // - 1 because of closing curly var postIndent = getLeadingIndent(content, this.range[1] - 1) insertions.push({pos: this.range[1] - 1, insertion: preserveIndent + exitInsert + postIndent, pri: 1}) } return insertions } function nodeTest(content, filename) { return ( this.parent != null && ( this.parent.type == "FunctionExpression" || this.parent.type == "FunctionDeclaration" ) ) } return scopetracer(mutator, nodeTest) } function needsRewrite(exitNode) { var doRewrite = false // crawl exitNode arguments to see if they contain a CallExpression or NewExpression est.traverse(exitNode, { enter: function enter(node, parent) { if (node.type == "FunctionExpression" || node.type == "FunctionDeclaration") { return est.VisitorOption.Skip } if (node.type == "CallExpression" || node.type == "NewExpression") { doRewrite = true } } }) return doRewrite }