UNPKG

strong-trace

Version:

StrongTrace Node.js Tracer

193 lines (171 loc) 5.15 kB
"use strict"; module.exports = scopenodes var esprima = require("esprima") var est = require("estraverse") var esprimaOptions = { loc: true, // to allow references to location in orig file range: true, // needed for replacement raw: false, tokens: false, comment: false, tolerant: true } var shebangExpr = /^\s*#\!.*?\n/m function scopenodes(content) { var scopes = [] // If the script has a shebang it will mess with esprima. hide it. var hasSheBang = shebangExpr.test(content) if (hasSheBang) { content = content.replace(/#\!/, "//") } var ast = esprima.parse(content, esprimaOptions) //console.log(JSON.stringify(ast, null, 2)) var path = [] // Track name assignment to avoid duplicate names var names = {} // Scope entry points: // parent: null, node: Program // parent: FunctionExpression, node: BlockStatement // parent: FunctionDeclaration, node: BlockStatement // TBD catch blocks are pseudo-scopes... function isScopeEntry(node, parent) { if (parent == null && node.type == "Program") { return true } if (node.type != "BlockStatement") { return false } if (parent.type == "FunctionExpression" || parent.type == "FunctionDeclaration") { return true } return false } est.traverse(ast, { enter: function testNode(node, parent) { // Look for a possible name candidate in case this ends up being anonymous if (node.type == "FunctionExpression") { if (parent.type == "AssignmentExpression") { if (parent.left.type == "MemberExpression") { node.maybeName = content.slice(parent.left.range[0], parent.left.range[1]) } // TBD other expected stuff here? } if (parent.type == "VariableDeclarator") { node.maybeName = parent.id.name } if (parent.type == "CallExpression" || parent.type == "NewExpression") { node.maybeName = nameFnArgument(content, parent) } if (parent.type == "Property") { node.maybeName = parent.key.name } } if (!isScopeEntry(node, parent)) { // This does not define a scope entry point //console.log("Skipping -- not a scope entry") return } //console.log("entering... (depth: %s)", nestDepth) //console.log(node) node.path = path.slice(0) node.parent = parent node.isStrict = isStrict(node) var name = getName(node) // Avoid name dupes if (names[name] == null) { names[name] = 1 } else { name = name + "{" + (++names[name]) + "}" } node.fnName = name scopes.push(node) path.push(node) }, leave: function leave(node, parent) { if (!isScopeEntry(node, parent)) { // This was not a scope block return } path.pop() //console.log("leaving... (depth: %s)", nestDepth) } }) return scopes } function isStrict(node) { if (node == null || node.body == null || node.body.length === 0) { return false } if (node.body[0].expression == null) { return false } return (node.body[0].expression.value == "use strict") } var complexRe = /.+[)].*(\.\w+)$/m function nameFnArgument(content, parent) { var caller = (parent.type == "NewExpression") ? "new " : "" var isListener = false if (parent.callee.type == "MemberExpression") { caller += content.slice(parent.callee.range[0], parent.callee.range[1]) if (/\.on$/.test(caller) && parent.arguments[0] && parent.arguments[0].type == "Literal") { // making the guess that this is a listener isListener = true caller += " '" + parent.arguments[0].value + "' listener" } } else if (parent.callee.type == "Identifier") { caller += parent.callee.name } else { // TBD any other cases we want to attempt? return "" } // Simplify overly-complex call expressiong, e.g. // foo.forEach(...).filter(...).sort(...) // TBD this doesn't catch foo.forEach(...)[0]() ... var tooComplex = caller.replace(/\r?\n/g, "").match(complexRe) if (tooComplex) { caller = tooComplex[1] } if (/\n|;/.test(caller)) { // Something ugly happened. return "" } if (isListener) { return caller } return caller + "() fn argument" } var proxyRe = /^__concurix/ function getName(node) { var names = [] if (node.path.length === 0) { return "" } for (var i = 0; i < node.path.length; i++) { var ancestor = node.path[i] if (ancestor.parent == null) { continue } else if (ancestor.parent.id && ancestor.parent.id.name) { names.push(ancestor.parent.id.name) } else if (ancestor.parent.maybeName && !proxyRe.test(ancestor.parent.maybeName)) { names.push(ancestor.parent.maybeName) } else { names.push("anonymous") } } if (node.parent.id && node.parent.id.name) { names.push(node.parent.id.name) } else if (node.parent.maybeName && !proxyRe.test(node.parent.maybeName)) { names.push(node.parent.maybeName) } else { names.push("anonymous") } return names.join(">") }