| /** |
| * @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); |
| } |
| |
| }; |