UNPKG

coffee-coverage

Version:

Istanbul and JSCoverage-style instrumentation for CoffeeScript files.

259 lines (229 loc) 9.76 kB
// Generated by CoffeeScript 2.3.2 (function() { var NodeWrapper, _, assert, coffeeScript, compile, forNodeAndChildren; assert = require('assert'); coffeeScript = require('coffeescript'); _ = require('lodash'); // Wraps a `node` returned from coffeescript's `nodes()` method. // Properties: // * `node` - The original coffeescript node. // * `parent` - A `NodeWrapper` object for the parent of the coffeescript node. // * `childName` - A coffeescript node has multiple named children. This is the name of the // attribute which contains this node in `@parent.node`. Note that `@parent.node[childName]` // may be a single Node or it may be an array of nodes, depending on the implementation of the // specific node type. // * `childIndex` - Where `@parent.node[childName]` is an array, this is the index of `@node` // in `@parent.node[childName]`. Note that inserting new nodes will obviously invalidate this // value, so this is more of a "hint" than a hard and fast truism. // * `depth` - The depth in the AST from the root node. // * `type` - Copy of @node.constructor.name. // * `locationData` - Copy of @node.locationData. // * `isStatement` - true if this node is a statement. module.exports = NodeWrapper = class NodeWrapper { constructor(node1, parent, childName1, childIndex1, depth = 0) { var ref, ref1, ref2; this.node = node1; this.parent = parent; this.childName = childName1; this.childIndex = childIndex1; this.depth = depth; assert(this.node); this.locationData = this.node.locationData; this.type = ((ref = this.node.constructor) != null ? ref.name : void 0) || null; // TODO: Is this too naive? coffeescript nodes have a `isStatement(o)` function, which // really only cares about `o.level`. Should we be working out the level and calling // this function instead of trying to figure this out ourselves? this.isStatement = (this.parent != null) && this.parent.type === 'Block' && this.childName === 'expressions'; // Note we exclude 'Value' nodes. When you parse a Class, you'll get Value nodes wrapping // each contiguous block of function assignments, and we don't want to treat these as // statements. I can't think of another case where you have a Value as a direct child // of an expression. if (this.isStatement && this.type === 'Value' && ((ref1 = this.parent.parent) != null ? ref1.type : void 0) === 'Class') { this.isStatement = ((ref2 = this.node.base.constructor) != null ? ref2.name : void 0) === "Call"; } this.isSwitchCases = this.childName === 'cases' && this.type === 'Array'; } // Run `fn(node)` for each child of this node. Child nodes will be automatically wrapped in a // `NodeWrapper`. forEachChild(fn) { if (this.node.children != null) { return this.node.children.forEach((childName) => { return this.forEachChildOfType(childName, fn); }); } } // Like `forEachChild`, but only forEachChildOfType(childName, fn) { var child, childNodes, children, index, results, wrappedChild; children = this.node[childName]; if (children != null) { childNodes = _.flatten([children], true); index = 0; results = []; while (index < childNodes.length) { child = childNodes[index]; if (child.constructor.name != null) { wrappedChild = new NodeWrapper(child, this, childName, index, this.depth + 1); fn(wrappedChild); } results.push(index++); } return results; } } // Mark this node and all descendants with the given flag. markAll(varName, value = true) { var markCoffeeNode; markCoffeeNode = function(coffeeNode) { if (coffeeNode.coffeeCoverage == null) { coffeeNode.coffeeCoverage = {}; } coffeeNode.coffeeCoverage[varName] = value; return coffeeNode.eachChild(markCoffeeNode); }; return markCoffeeNode(this.node); } // Mark a node with a flag. mark(varName, value = true) { var base; if ((base = this.node).coffeeCoverage == null) { base.coffeeCoverage = {}; } return this.node.coffeeCoverage[varName] = value; } isMarked(varName, value = true) { var ref; return ((ref = this.node.coffeeCoverage) != null ? ref[varName] : void 0) === value; } // Returns a NodeWrapper for the given child. This only works if the child is not an array // (e.g. `Block.expressions`) child(name, index = null) { var child; child = this.node[name]; if (!child) { return null; } if (index == null) { assert(!_.isArray(child)); return new NodeWrapper(child, this, name, 0, this.depth + 1); } else { assert(_.isArray(child)); if (!child[index]) { return null; } return new NodeWrapper(child[index], this, name, index, this.depth + 1); } } // `@childIndex` is a hint, since nodes can move around. This updateds @childIndex if // necessary. _fixChildIndex() { var childIndex; if (!_.isArray(this.parent.node[this.childName])) { return this.childIndex = 0; } else { if (this.parent.node[this.childName][this.childIndex] !== this.node) { childIndex = _.indexOf(this.parent.node[this.childName], this.node); if (childIndex === -1) { throw new Error("Can't find node in parent"); } return this.childIndex = childIndex; } } } // Returns this node's next sibling, or null if this node has no next sibling. next() { var nextNode, ref; if ((ref = this.parent.type) !== 'Block' && ref !== 'Obj') { return null; } this._fixChildIndex(); nextNode = this.parent.node[this.childName][this.childIndex + 1]; if (nextNode == null) { return null; } else { return new NodeWrapper(nextNode, this.parent, this.childName, this.childIndex + 1, this.depth); } } _insertBeforeIndex(childName, index, csSource) { var compiled; assert(_.isArray(this.node[childName]), `${this.toString()} -> ${childName}`); compiled = compile(csSource, this.node); return this.node[childName].splice(index, 0, compiled); } // Insert a new node before this node (only works if this node is in an array-based attribute, // like `Block.expressions`.) // Note that generated nodes will have the `node.coffeeCoverage.generated` flag set, // and will be skipped when instrumenting code. insertBefore(csSource) { this._fixChildIndex(); return this.parent._insertBeforeIndex(this.childName, this.childIndex, csSource); } insertAfter(csSource) { this._fixChildIndex(); return this.parent._insertBeforeIndex(this.childName, this.childIndex + 1, csSource); } // Insert a chunk of code at the start of a child of this node. E.g. if this is a Block, // then `insertAtStart('expressions', 'console.log "foo"'')` would add a `console.log` // statement to the start of the Block's expressions list. // Note that generated nodes will have the `node.coffeeCoverage.generated` flag set, // and will be skipped when instrumenting code. insertAtStart(childName, csSource) { var child, ref; child = this.node[childName]; if (this.type === 'Block' && childName === 'expressions') { if (!child) { return this.node[childName] = [compile(csSource, this.node)]; } else { return this.node[childName].unshift(compile(csSource, this.node)); } } else if ((child != null ? (ref = child.constructor) != null ? ref.name : void 0 : void 0) === 'Block') { return child.expressions.unshift(compile(csSource, child)); } else if (!child) { // This will generate a 'Block' return this.node[childName] = compile(csSource, this.node); } else { throw new Error(`Don't know how to insert statement into ${this.type}.${childName}: ${this.type[childName]}`); } } toString() { var answer, ref; answer = ''; if (this.childName) { answer += `${this.childName}[${this.childIndex}]:`; } answer += this.type; if (this.node.locationData != null) { answer += ` (${((ref = this.node.locationData) != null ? ref.first_line : void 0) + 1}:${this.node.locationData.first_column + 1})`; } return answer; } }; forNodeAndChildren = function(node, fn) { fn(node); return node.eachChild(fn); }; compile = function(csSource, node) { var compiled, line; compiled = coffeeScript.nodes(csSource); // In latest coffee-script, some blocks do not have locationData? line = !node.locationData ? 0 : line = node.locationData.first_line; forNodeAndChildren(compiled, function(n) { // Fix up location data for each instrumented line. Make these all 0-length, // so we don't have to rewrite the location data for all the non-generated // nodes in the tree. n.locationData = { first_line: line - 1, // -1 because `line` is 1-based first_column: 0, last_line: line - 1, last_column: 0 }; // Mark each node as coffee-coverage generated, so we won't try to instrument our // instrumented lines. if (n.coffeeCoverage == null) { n.coffeeCoverage = {}; } return n.coffeeCoverage.generated = true; }); return compiled; }; }).call(this);