/** * @fileoverview Attaches comments to the AST. * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ var astNodeTypes = require("./ast-node-types"); //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ var extra = { trailingComments: [], leadingComments: [], bottomRightStack: [], previousNode: null }; //------------------------------------------------------------------------------ // Public //------------------------------------------------------------------------------ module.exports = { reset: function() { extra.trailingComments = []; extra.leadingComments = []; extra.bottomRightStack = []; extra.previousNode = null; }, addComment: function(comment) { extra.trailingComments.push(comment); extra.leadingComments.push(comment); }, processComment: function(node) { var lastChild, trailingComments, i, j; if (node.type === astNodeTypes.Program) { if (node.body.length > 0) { return; } } if (extra.trailingComments.length > 0) { /* * If the first comment in trailingComments comes after the * current node, then we're good - all comments in the array will * come after the node and so it's safe to add then as official * trailingComments. */ if (extra.trailingComments[0].range[0] >= node.range[1]) { trailingComments = extra.trailingComments; extra.trailingComments = []; } else { /* * Otherwise, if the first comment doesn't come after the * current node, that means we have a mix of leading and trailing * comments in the array and that leadingComments contains the * same items as trailingComments. Reset trailingComments to * zero items and we'll handle this by evaluating leadingComments * later. */ extra.trailingComments.length = 0; } } else { if (extra.bottomRightStack.length > 0 && extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments && extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments[0].range[0] >= node.range[1]) { trailingComments = extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments; delete extra.bottomRightStack[extra.bottomRightStack.length - 1].trailingComments; } } // Eating the stack. while (extra.bottomRightStack.length > 0 && extra.bottomRightStack[extra.bottomRightStack.length - 1].range[0] >= node.range[0]) { lastChild = extra.bottomRightStack.pop(); } if (lastChild) { if (lastChild.leadingComments) { if (lastChild.leadingComments[lastChild.leadingComments.length - 1].range[1] <= node.range[0]) { node.leadingComments = lastChild.leadingComments; delete lastChild.leadingComments; } else { // A leading comment for an anonymous class had been stolen by its first MethodDefinition, // so this takes back the leading comment. // See Also: https://github.com/eslint/espree/issues/158 for (i = lastChild.leadingComments.length - 2; i >= 0; --i) { if (lastChild.leadingComments[i].range[1] <= node.range[0]) { node.leadingComments = lastChild.leadingComments.splice(0, i + 1); break; } } } } } else if (extra.leadingComments.length > 0) { if (extra.leadingComments[extra.leadingComments.length - 1].range[1] <= node.range[0]) { if (extra.previousNode) { for (j = 0; j < extra.leadingComments.length; j++) { if (extra.leadingComments[j].end < extra.previousNode.end) { extra.leadingComments.splice(j, 1); j--; } } } if (extra.leadingComments.length > 0) { node.leadingComments = extra.leadingComments; extra.leadingComments = []; } } else { // https://github.com/eslint/espree/issues/2 /* * In special cases, such as return (without a value) and * debugger, all comments will end up as leadingComments and * will otherwise be eliminated. This extra step runs when the * bottomRightStack is empty and there are comments left * in leadingComments. * * This loop figures out the stopping point between the actual * leading and trailing comments by finding the location of the * first comment that comes after the given node. */ for (i = 0; i < extra.leadingComments.length; i++) { if (extra.leadingComments[i].range[1] > node.range[0]) { break; } } /* * Split the array based on the location of the first comment * that comes after the node. Keep in mind that this could * result in an empty array, and if so, the array must be * deleted. */ node.leadingComments = extra.leadingComments.slice(0, i); if (node.leadingComments.length === 0) { delete node.leadingComments; } /* * Similarly, trailing comments are attached later. The variable * must be reset to null if there are no trailing comments. */ trailingComments = extra.leadingComments.slice(i); if (trailingComments.length === 0) { trailingComments = null; } } } extra.previousNode = node; if (trailingComments) { node.trailingComments = trailingComments; } extra.bottomRightStack.push(node); } };