Created
August 28, 2014 16:49
-
-
Save eventualbuddha/1f74d3bbda0a870a007c to your computer and use it in GitHub Desktop.
Testing matching block comments to AST nodes
This file contains hidden or 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
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