Skip to content

Instantly share code, notes, and snippets.

@eventualbuddha
Created August 28, 2014 16:49
Show Gist options
  • Save eventualbuddha/1f74d3bbda0a870a007c to your computer and use it in GitHub Desktop.
Save eventualbuddha/1f74d3bbda0a870a007c to your computer and use it in GitHub Desktop.
Testing matching block comments to AST nodes
var types = require('ast-types');
var n = types.namedTypes;
var esprima = require('esprima');
function printCommentsAndNode(ast, calculatePositionOffset) {
ast.comments.forEach(function(comment) {
if (comment.type !== 'Block' || comment.value[0] !== '*') { return; }
var associatedNode = findAssociatedNodeInAstForComment(ast, comment, calculatePositionOffset);
console.log('COMMENT');
console.log();
console.log('/*' + comment.value + '*/');
console.log();
console.log('INFO');
console.log();
console.log('name =', inferNameFromNode(associatedNode));
console.log();
console.log();
});
}
/**
* Infers the full name of the given AST node. For example:
*
* ```js
* // Inferred name "foo".
* function foo() {}
*
* // Inferred name "A".
* var A = {};
*
* var A = {
* // Inferred name "A.B".
* B: 1
* };
* ```
*
* @param {Node} node
* @return {string}
*/
function inferNameFromNode(node) {
if (n.FunctionDeclaration.check(node)) {
return node.id.name;
} else if (n.VariableDeclaration.check(node)) {
return node.declarations[0].id.name;
} else if (n.Property.check(node)) {
var leaf = n.Identifier.check(node.key) ?
node.key.name :
n.Literal.check(node.key) ?
node.key.value :
undefined;
if (leaf) {
var parent = node.parent;
while (parent) {
var parentName = inferNameFromNode(parent);
if (parentName) {
// TODO: Determine the correct parent/child relationship.
return parentName + '.' + leaf;
}
parent = parent.parent;
}
}
} else {
console.error('failed to infer name for', node.type);
}
}
function findAssociatedNodeInAstForComment(ast, comment, calculatePositionOffset) {
return findNextNodeInAstAfterPosition(ast, comment.loc.end, calculatePositionOffset);
}
function findNextNodeInAstAfterPosition(ast, pos, calculatePositionOffset) {
var closestNode = null;
var posOffset = calculatePositionOffset(pos);
var closestDistance = Infinity;
types.visit(ast, {
/**
* Visits each AST node.
*
* @param {NodePath} path
* @return {?boolean}
*/
visitNode: function(path) {
path.node.parent = path.parent && path.parent.node;
var loc = path.node.loc;
var endOffset = calculatePositionOffset(loc.end);
if (endOffset < posOffset) {
// Don't bother looking at nodes ending before the position.
return false;
}
var startOffset = calculatePositionOffset(loc.start);
if (startOffset < posOffset) {
// This node starts before the position, so it encompases the position.
// We're looking for one of our children.
this.traverse(path);
} else {
// This node starts after the position, so it's a candidate.
var distance = startOffset - posOffset;
if (distance < closestDistance) {
// This is the closest node we've found so far.
closestDistance = distance;
closestNode = path.node;
}
// None of our children can be closer than we are, so skip them.
return false;
}
}
});
return closestNode;
}
/**
* Create a function that gets the offset for a given source location.
*
* @param {string} source
* @return {function({line: number, column: number}): number}
*/
function makePositionOffsetCalculator(source) {
var offset = 0;
var lineOffsets = [];
var EOL = '\n';
var EOL_LENGTH = EOL.length;
var index = -EOL_LENGTH;
do {
lineOffsets[index++] = offset + EOL_LENGTH;
offset = source.indexOf(EOL, offset + EOL_LENGTH);
} while (offset > 0);
return function(pos) {
var lineno = pos.line - /* index offset */ 1;
var offsetFromLines = lineno <= 0 ? 0 : lineOffsets[
lineno - /* don't count this line */ 1
];
return offsetFromLines + pos.column;
};
}
var fs = require('fs');
fs.readFile(process.argv[2] || __filename, 'utf8', function(err, body) {
printCommentsAndNode(
esprima.parse(body, { comment: true, loc: true }),
makePositionOffsetCalculator(body)
);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment