Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: support attachComment #483

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ const options = {
// create a top-level comments array containing all comments
comment: false,

// attach comments to the closest relevant node as leadingComments and trailingComments
attachComment: false,

// create a top-level tokens array containing all tokens
tokens: false,

Expand Down
185 changes: 185 additions & 0 deletions lib/comment-attachment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* @fileoverview Attaches comments to the AST.
* @author Nicholas C. Zakas
*/


//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

import astNodeTypes from "./ast-node-types";

//------------------------------------------------------------------------------
// Private
//------------------------------------------------------------------------------

const extra = {
trailingComments: [],
leadingComments: [],
bottomRightStack: [],
previousNode: null
};

//------------------------------------------------------------------------------
// Public
//------------------------------------------------------------------------------

/**
* reset the store
* @returns {void}
*/
export function reset() {
extra.trailingComments = [];
extra.leadingComments = [];
extra.bottomRightStack = [];
extra.previousNode = null;
}

/**
* add comment to the store
* @param {Object} comment The comment object.
* @returns {void}
*/
export function addComment(comment) {
extra.trailingComments.push(comment);
extra.leadingComments.push(comment);
}

/**
* Process the given node against the stored comments
* @param {ASTNode} node The node to process.
* @returns {void}
*/
export function processComment(node) {
let 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);
}
17 changes: 15 additions & 2 deletions lib/espree.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-param-reassign*/
import TokenTranslator from "./token-translator.js";
import { normalizeOptions } from "./options.js";
import * as commentAttachment from "./comment-attachment";


const STATE = Symbol("espree's internal state");
Expand Down Expand Up @@ -69,7 +70,7 @@ export default () => Parser => {
// TODO: use {...options} when spread is supported(Node.js >= 8.3.0).
ecmaVersion: options.ecmaVersion,
sourceType: options.sourceType,
ranges: options.ranges,
ranges: options.ranges || options.attachComment,
locations: options.locations,

// Truthy value is true for backward compatibility.
Expand All @@ -93,14 +94,22 @@ export default () => Parser => {
const comment = convertAcornCommentToEsprimaComment(block, text, start, end, startLoc, endLoc);

this[STATE].comments.push(comment);

if (options.attachComment === true) {
commentAttachment.addComment(comment);
}
}
}
}, code);

// TODO: remove global state.
commentAttachment.reset();

// Initialize internal state.
this[STATE] = {
tokens: tokenTranslator ? [] : null,
comments: options.comment === true ? [] : null,
comments: options.comment === true || options.attachComment === true ? [] : null,
attachComment: options.attachComment === true,
impliedStrict: ecmaFeatures.impliedStrict === true && this.options.ecmaVersion >= 5,
ecmaVersion: this.options.ecmaVersion,
jsxAttrValueToken: false,
Expand Down Expand Up @@ -275,6 +284,10 @@ export default () => Parser => {
}
}

if (this[STATE].attachComment) {
commentAttachment.processComment(result);
}

if (result.type.includes("Function") && !result.generator) {
result.generator = false;
}
Expand Down