GGD/node_modules/java-parser/src/comments.js

312 lines
8.8 KiB
JavaScript

"use strict";
const findLast = require("lodash/findLast");
/**
* Search where is the position of the comment in the token array by
* using dichotomic search.
* @param {*} tokens ordered array of tokens
* @param {*} comment comment token
* @return the position of the token next to the comment
*/
function findUpperBoundToken(tokens, comment) {
let diff;
let i;
let current;
let len = tokens.length;
i = 0;
while (len) {
diff = len >>> 1;
current = i + diff;
if (tokens[current].startOffset > comment.startOffset) {
len = diff;
} else {
i = current + 1;
len -= diff + 1;
}
}
return i;
}
function isPrettierIgnoreComment(comment) {
return comment.image.match(
/(\/\/(\s*)prettier-ignore(\s*))|(\/\*(\s*)prettier-ignore(\s*)\*\/)/gm
);
}
function isFormatterOffOnComment(comment) {
return comment.image.match(
/(\/\/(\s*)@formatter:(off|on)(\s*))|(\/\*(\s*)@formatter:(off|on)(\s*)\*\/)/gm
);
}
/**
* Pre-processing of tokens in order to
* complete the parser's mostEnclosiveCstNodeByStartOffset and mostEnclosiveCstNodeByEndOffset structures.
*
* @param {ITokens[]} tokens - array of tokens
* @param {{[startOffset: number]: CSTNode}} mostEnclosiveCstNodeByStartOffset
* @param {{[endOffset: number]: CSTNode}} mostEnclosiveCstNodeByEndOffset
*/
function completeMostEnclosiveCSTNodeByOffset(
tokens,
mostEnclosiveCstNodeByStartOffset,
mostEnclosiveCstNodeByEndOffset
) {
tokens.forEach(token => {
if (mostEnclosiveCstNodeByStartOffset[token.startOffset] === undefined) {
mostEnclosiveCstNodeByStartOffset[token.startOffset] = token;
}
if (mostEnclosiveCstNodeByEndOffset[token.endOffset] === undefined) {
mostEnclosiveCstNodeByEndOffset[token.endOffset] = token;
}
});
}
function extendRangeOffset(comments, tokens) {
let position;
comments.forEach(comment => {
position = findUpperBoundToken(tokens, comment);
const extendedStartOffset =
position - 1 < 0 ? comment.startOffset : tokens[position - 1].endOffset;
const extendedEndOffset =
position == tokens.length
? comment.endOffset
: tokens[position].startOffset;
comment.extendedOffset = {
startOffset: extendedStartOffset,
endOffset: extendedEndOffset
};
});
}
/**
* Create two data structures we use to know at which offset a comment can be attached.
* - commentsByExtendedStartOffset: map a comment by the endOffset of the previous token.
* - commentsByExtendedEndOffset: map a comment by the startOffset of the next token
*
* @param {ITokens[]} tokens - array of tokens
*
* @return {{commentsByExtendedStartOffset: {[extendedStartOffset: number]: Comment[]}, commentsByExtendedEndOffset: {[extendedEndOffset: number]: Comment[]}}}
*/
function mapCommentsByExtendedRange(comments) {
const commentsByExtendedEndOffset = {};
const commentsByExtendedStartOffset = {};
comments.forEach(comment => {
const extendedStartOffset = comment.extendedOffset.startOffset;
const extendedEndOffset = comment.extendedOffset.endOffset;
if (commentsByExtendedEndOffset[extendedEndOffset] === undefined) {
commentsByExtendedEndOffset[extendedEndOffset] = [comment];
} else {
commentsByExtendedEndOffset[extendedEndOffset].push(comment);
}
if (commentsByExtendedStartOffset[extendedStartOffset] === undefined) {
commentsByExtendedStartOffset[extendedStartOffset] = [comment];
} else {
commentsByExtendedStartOffset[extendedStartOffset].push(comment);
}
});
return { commentsByExtendedEndOffset, commentsByExtendedStartOffset };
}
/**
* Determine if a comment should be attached as a trailing comment to a specific node.
* A comment should be trailing if it is on the same line than the previous token and
* not on the same line than the next token
*
* @param {*} comment
* @param {CSTNode} node
* @param {{[startOffset: number]: CSTNode}} mostEnclosiveCstNodeByStartOffset
*/
function shouldAttachTrailingComments(
comment,
node,
mostEnclosiveCstNodeByStartOffset
) {
if (isPrettierIgnoreComment(comment)) {
return false;
}
const nextNode =
mostEnclosiveCstNodeByStartOffset[comment.extendedOffset.endOffset];
// Last node of the file
if (nextNode === undefined) {
return true;
}
const nodeEndLine =
node.location !== undefined ? node.location.endLine : node.endLine;
if (comment.startLine !== nodeEndLine) {
return false;
}
const nextNodeStartLine =
nextNode.location !== undefined
? nextNode.location.startLine
: nextNode.startLine;
return comment.endLine !== nextNodeStartLine;
}
/**
* Attach comments to the most enclosive CSTNode (node or token)
*
* @param {ITokens[]} tokens
* @param {*} comments
* @param {{[startOffset: number]: CSTNode}} mostEnclosiveCstNodeByStartOffset
* @param {{[endOffset: number]: CSTNode}} mostEnclosiveCstNodeByEndOffset
*/
function attachComments(
tokens,
comments,
mostEnclosiveCstNodeByStartOffset,
mostEnclosiveCstNodeByEndOffset
) {
// Edge case: only comments in the file
if (tokens.length === 0) {
mostEnclosiveCstNodeByStartOffset[NaN].leadingComments = comments;
return;
}
// Pre-processing phase to complete the data structures we need to attach
// a comment to the right place
completeMostEnclosiveCSTNodeByOffset(
tokens,
mostEnclosiveCstNodeByStartOffset,
mostEnclosiveCstNodeByEndOffset
);
extendRangeOffset(comments, tokens);
const { commentsByExtendedStartOffset, commentsByExtendedEndOffset } =
mapCommentsByExtendedRange(comments);
/*
This set is here to ensure that we attach comments only once
If a comment is attached to a node or token, we remove it from this set
*/
const commentsToAttach = new Set(comments);
// Attach comments as trailing comments if desirable
Object.keys(mostEnclosiveCstNodeByEndOffset).forEach(endOffset => {
// We look if some comments is directly following this node/token
if (commentsByExtendedStartOffset[endOffset] !== undefined) {
const nodeTrailingComments = commentsByExtendedStartOffset[
endOffset
].filter(comment => {
return (
shouldAttachTrailingComments(
comment,
mostEnclosiveCstNodeByEndOffset[endOffset],
mostEnclosiveCstNodeByStartOffset
) && commentsToAttach.has(comment)
);
});
if (nodeTrailingComments.length > 0) {
mostEnclosiveCstNodeByEndOffset[endOffset].trailingComments =
nodeTrailingComments;
}
nodeTrailingComments.forEach(comment => {
commentsToAttach.delete(comment);
});
}
});
// Attach rest of comments as leading comments
Object.keys(mostEnclosiveCstNodeByStartOffset).forEach(startOffset => {
// We look if some comments is directly preceding this node/token
if (commentsByExtendedEndOffset[startOffset] !== undefined) {
const nodeLeadingComments = commentsByExtendedEndOffset[
startOffset
].filter(comment => commentsToAttach.has(comment));
if (nodeLeadingComments.length > 0) {
mostEnclosiveCstNodeByStartOffset[startOffset].leadingComments =
nodeLeadingComments;
}
// prettier ignore support
for (let i = 0; i < nodeLeadingComments.length; i++) {
if (isPrettierIgnoreComment(nodeLeadingComments[i])) {
mostEnclosiveCstNodeByStartOffset[startOffset].ignore = true;
break;
}
}
}
});
}
/**
* Create pairs of formatter:off and formatter:on
* @param comments
* @returns pairs of formatter:off and formatter:on
*/
function matchFormatterOffOnPairs(comments) {
const onOffComments = comments.filter(comment =>
isFormatterOffOnComment(comment)
);
let isPreviousCommentOff = false;
let isCurrentCommentOff = true;
const pairs = [];
let paired = {};
onOffComments.forEach(comment => {
isCurrentCommentOff = comment.image.slice(-3) === "off";
if (!isPreviousCommentOff) {
if (isCurrentCommentOff) {
paired.off = comment;
}
} else {
if (!isCurrentCommentOff) {
paired.on = comment;
pairs.push(paired);
paired = {};
}
}
isPreviousCommentOff = isCurrentCommentOff;
});
if (onOffComments.length > 0 && isCurrentCommentOff) {
paired.on = undefined;
pairs.push(paired);
}
return pairs;
}
/**
* Check if the node is between formatter:off and formatter:on and change his ignore state
* @param node
* @param commentPairs
*/
function shouldNotFormat(node, commentPairs) {
const matchingPair = findLast(
commentPairs,
comment => comment.off.endOffset < node.location.startOffset
);
if (
matchingPair !== undefined &&
(matchingPair.on === undefined ||
matchingPair.on.startOffset > node.location.endOffset)
) {
node.ignore = true;
}
}
module.exports = {
matchFormatterOffOnPairs,
shouldNotFormat,
attachComments
};