969 lines
26 KiB
JavaScript
969 lines
26 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("assert");
|
|
const docBuilders = require("./doc-builders");
|
|
const concat = docBuilders.concat;
|
|
const hardline = docBuilders.hardline;
|
|
const breakParent = docBuilders.breakParent;
|
|
const indent = docBuilders.indent;
|
|
const lineSuffix = docBuilders.lineSuffix;
|
|
const join = docBuilders.join;
|
|
const util = require("./util");
|
|
const childNodesCacheKey = Symbol("child-nodes");
|
|
const locStart = util.locStart;
|
|
const locEnd = util.locEnd;
|
|
const getNextNonSpaceNonCommentCharacter =
|
|
util.getNextNonSpaceNonCommentCharacter;
|
|
|
|
function getSortedChildNodes(node, text, resultArray) {
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
if (resultArray) {
|
|
if (
|
|
node &&
|
|
node.type &&
|
|
node.type !== "CommentBlock" &&
|
|
node.type !== "CommentLine" &&
|
|
node.type !== "Line" &&
|
|
node.type !== "Block" &&
|
|
node.type !== "EmptyStatement" &&
|
|
node.type !== "TemplateElement"
|
|
) {
|
|
// This reverse insertion sort almost always takes constant
|
|
// time because we almost always (maybe always?) append the
|
|
// nodes in order anyway.
|
|
let i;
|
|
for (i = resultArray.length - 1; i >= 0; --i) {
|
|
if (
|
|
locStart(resultArray[i]) <= locStart(node) &&
|
|
locEnd(resultArray[i]) <= locEnd(node)
|
|
) {
|
|
break;
|
|
}
|
|
}
|
|
resultArray.splice(i + 1, 0, node);
|
|
return;
|
|
}
|
|
} else if (node[childNodesCacheKey]) {
|
|
return node[childNodesCacheKey];
|
|
}
|
|
|
|
let names;
|
|
if (node && typeof node === "object") {
|
|
names = Object.keys(node).filter(
|
|
n =>
|
|
n !== "enclosingNode" && n !== "precedingNode" && n !== "followingNode"
|
|
);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (!resultArray) {
|
|
Object.defineProperty(node, childNodesCacheKey, {
|
|
value: (resultArray = []),
|
|
enumerable: false
|
|
});
|
|
}
|
|
|
|
for (let i = 0, nameCount = names.length; i < nameCount; ++i) {
|
|
getSortedChildNodes(node[names[i]], text, resultArray);
|
|
}
|
|
|
|
return resultArray;
|
|
}
|
|
|
|
// As efficiently as possible, decorate the comment object with
|
|
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
|
|
// least one of which is guaranteed to be defined.
|
|
function decorateComment(node, comment, text) {
|
|
const childNodes = getSortedChildNodes(node, text);
|
|
let precedingNode, followingNode;
|
|
// Time to dust off the old binary search robes and wizard hat.
|
|
let left = 0, right = childNodes.length;
|
|
while (left < right) {
|
|
const middle = (left + right) >> 1;
|
|
const child = childNodes[middle];
|
|
|
|
if (
|
|
locStart(child) - locStart(comment) <= 0 &&
|
|
locEnd(comment) - locEnd(child) <= 0
|
|
) {
|
|
// The comment is completely contained by this child node.
|
|
comment.enclosingNode = child;
|
|
|
|
decorateComment(child, comment, text);
|
|
return; // Abandon the binary search at this level.
|
|
}
|
|
|
|
if (locEnd(child) - locStart(comment) <= 0) {
|
|
// This child node falls completely before the comment.
|
|
// Because we will never consider this node or any nodes
|
|
// before it again, this node must be the closest preceding
|
|
// node we have encountered so far.
|
|
precedingNode = child;
|
|
left = middle + 1;
|
|
continue;
|
|
}
|
|
|
|
if (locEnd(comment) - locStart(child) <= 0) {
|
|
// This child node falls completely after the comment.
|
|
// Because we will never consider this node or any nodes after
|
|
// it again, this node must be the closest following node we
|
|
// have encountered so far.
|
|
followingNode = child;
|
|
right = middle;
|
|
continue;
|
|
}
|
|
|
|
throw new Error("Comment location overlaps with node location");
|
|
}
|
|
|
|
// We don't want comments inside of different expressions inside of the same
|
|
// template literal to move to another expression.
|
|
if (
|
|
comment.enclosingNode &&
|
|
comment.enclosingNode.type === "TemplateLiteral"
|
|
) {
|
|
const quasis = comment.enclosingNode.quasis;
|
|
const commentIndex = findExpressionIndexForComment(quasis, comment);
|
|
|
|
if (
|
|
precedingNode &&
|
|
findExpressionIndexForComment(quasis, precedingNode) !== commentIndex
|
|
) {
|
|
precedingNode = null;
|
|
}
|
|
if (
|
|
followingNode &&
|
|
findExpressionIndexForComment(quasis, followingNode) !== commentIndex
|
|
) {
|
|
followingNode = null;
|
|
}
|
|
}
|
|
|
|
if (precedingNode) {
|
|
comment.precedingNode = precedingNode;
|
|
}
|
|
|
|
if (followingNode) {
|
|
comment.followingNode = followingNode;
|
|
}
|
|
}
|
|
|
|
function attach(comments, ast, text) {
|
|
if (!Array.isArray(comments)) {
|
|
return;
|
|
}
|
|
|
|
const tiesToBreak = [];
|
|
|
|
comments.forEach((comment, i) => {
|
|
decorateComment(ast, comment, text);
|
|
|
|
const precedingNode = comment.precedingNode;
|
|
const enclosingNode = comment.enclosingNode;
|
|
const followingNode = comment.followingNode;
|
|
|
|
const isLastComment = comments.length - 1 === i;
|
|
|
|
if (util.hasNewline(text, locStart(comment), { backwards: true })) {
|
|
// If a comment exists on its own line, prefer a leading comment.
|
|
// We also need to check if it's the first line of the file.
|
|
if (
|
|
handleLastFunctionArgComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
|
|
handleIfStatementComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleTryStatementComments(enclosingNode, followingNode, comment) ||
|
|
handleClassComments(enclosingNode, comment) ||
|
|
handleImportSpecifierComments(enclosingNode, comment) ||
|
|
handleObjectPropertyComments(enclosingNode, comment) ||
|
|
handleForComments(enclosingNode, precedingNode, comment) ||
|
|
handleUnionTypeComments(
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
|
|
handleImportDeclarationComments(
|
|
text,
|
|
enclosingNode,
|
|
precedingNode,
|
|
comment
|
|
) ||
|
|
handleAssignmentPatternComments(enclosingNode, comment)
|
|
) {
|
|
// We're good
|
|
} else if (followingNode) {
|
|
// Always a leading comment.
|
|
addLeadingComment(followingNode, comment);
|
|
} else if (precedingNode) {
|
|
addTrailingComment(precedingNode, comment);
|
|
} else if (enclosingNode) {
|
|
addDanglingComment(enclosingNode, comment);
|
|
} else {
|
|
// There are no nodes, let's attach it to the root of the ast
|
|
addDanglingComment(ast, comment);
|
|
}
|
|
} else if (util.hasNewline(text, locEnd(comment))) {
|
|
if (
|
|
handleLastFunctionArgComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleConditionalExpressionComments(
|
|
enclosingNode,
|
|
precedingNode,
|
|
followingNode,
|
|
comment,
|
|
text
|
|
) ||
|
|
handleImportSpecifierComments(enclosingNode, comment) ||
|
|
handleIfStatementComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleClassComments(enclosingNode, comment) ||
|
|
handleLabeledStatementComments(enclosingNode, comment) ||
|
|
handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
|
|
handlePropertyComments(enclosingNode, comment) ||
|
|
handleExportNamedDeclarationComments(enclosingNode, comment) ||
|
|
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
|
|
handleClassMethodComments(enclosingNode, comment) ||
|
|
handleTypeAliasComments(enclosingNode, followingNode, comment) ||
|
|
handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
|
|
) {
|
|
// We're good
|
|
} else if (precedingNode) {
|
|
// There is content before this comment on the same line, but
|
|
// none after it, so prefer a trailing comment of the previous node.
|
|
addTrailingComment(precedingNode, comment);
|
|
} else if (followingNode) {
|
|
addLeadingComment(followingNode, comment);
|
|
} else if (enclosingNode) {
|
|
addDanglingComment(enclosingNode, comment);
|
|
} else {
|
|
// There are no nodes, let's attach it to the root of the ast
|
|
addDanglingComment(ast, comment);
|
|
}
|
|
} else {
|
|
if (
|
|
handleIfStatementComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) ||
|
|
handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) ||
|
|
handleCommentInEmptyParens(text, enclosingNode, comment) ||
|
|
handleOnlyComments(enclosingNode, ast, comment, isLastComment)
|
|
) {
|
|
// We're good
|
|
} else if (precedingNode && followingNode) {
|
|
// Otherwise, text exists both before and after the comment on
|
|
// the same line. If there is both a preceding and following
|
|
// node, use a tie-breaking algorithm to determine if it should
|
|
// be attached to the next or previous node. In the last case,
|
|
// simply attach the right node;
|
|
const tieCount = tiesToBreak.length;
|
|
if (tieCount > 0) {
|
|
const lastTie = tiesToBreak[tieCount - 1];
|
|
if (lastTie.followingNode !== comment.followingNode) {
|
|
breakTies(tiesToBreak, text);
|
|
}
|
|
}
|
|
tiesToBreak.push(comment);
|
|
} else if (precedingNode) {
|
|
addTrailingComment(precedingNode, comment);
|
|
} else if (followingNode) {
|
|
addLeadingComment(followingNode, comment);
|
|
} else if (enclosingNode) {
|
|
addDanglingComment(enclosingNode, comment);
|
|
} else {
|
|
// There are no nodes, let's attach it to the root of the ast
|
|
addDanglingComment(ast, comment);
|
|
}
|
|
}
|
|
});
|
|
|
|
breakTies(tiesToBreak, text);
|
|
|
|
comments.forEach(comment => {
|
|
// These node references were useful for breaking ties, but we
|
|
// don't need them anymore, and they create cycles in the AST that
|
|
// may lead to infinite recursion if we don't delete them here.
|
|
delete comment.precedingNode;
|
|
delete comment.enclosingNode;
|
|
delete comment.followingNode;
|
|
});
|
|
}
|
|
|
|
function breakTies(tiesToBreak, text) {
|
|
const tieCount = tiesToBreak.length;
|
|
if (tieCount === 0) {
|
|
return;
|
|
}
|
|
|
|
const precedingNode = tiesToBreak[0].precedingNode;
|
|
const followingNode = tiesToBreak[0].followingNode;
|
|
let gapEndPos = locStart(followingNode);
|
|
|
|
// Iterate backwards through tiesToBreak, examining the gaps
|
|
// between the tied comments. In order to qualify as leading, a
|
|
// comment must be separated from followingNode by an unbroken series of
|
|
// whitespace-only gaps (or other comments).
|
|
let indexOfFirstLeadingComment;
|
|
for (
|
|
indexOfFirstLeadingComment = tieCount;
|
|
indexOfFirstLeadingComment > 0;
|
|
--indexOfFirstLeadingComment
|
|
) {
|
|
const comment = tiesToBreak[indexOfFirstLeadingComment - 1];
|
|
assert.strictEqual(comment.precedingNode, precedingNode);
|
|
assert.strictEqual(comment.followingNode, followingNode);
|
|
|
|
const gap = text.slice(locEnd(comment), gapEndPos);
|
|
if (/\S/.test(gap)) {
|
|
// The gap string contained something other than whitespace.
|
|
break;
|
|
}
|
|
|
|
gapEndPos = locStart(comment);
|
|
}
|
|
|
|
tiesToBreak.forEach((comment, i) => {
|
|
if (i < indexOfFirstLeadingComment) {
|
|
addTrailingComment(precedingNode, comment);
|
|
} else {
|
|
addLeadingComment(followingNode, comment);
|
|
}
|
|
});
|
|
|
|
tiesToBreak.length = 0;
|
|
}
|
|
|
|
function addCommentHelper(node, comment) {
|
|
const comments = node.comments || (node.comments = []);
|
|
comments.push(comment);
|
|
comment.printed = false;
|
|
}
|
|
|
|
function addLeadingComment(node, comment) {
|
|
comment.leading = true;
|
|
comment.trailing = false;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function addDanglingComment(node, comment) {
|
|
comment.leading = false;
|
|
comment.trailing = false;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function addTrailingComment(node, comment) {
|
|
comment.leading = false;
|
|
comment.trailing = true;
|
|
addCommentHelper(node, comment);
|
|
}
|
|
|
|
function addBlockStatementFirstComment(node, comment) {
|
|
const body = node.body.filter(n => n.type !== "EmptyStatement");
|
|
if (body.length === 0) {
|
|
addDanglingComment(node, comment);
|
|
} else {
|
|
addLeadingComment(body[0], comment);
|
|
}
|
|
}
|
|
|
|
function addBlockOrNotComment(node, comment) {
|
|
if (node.type === "BlockStatement") {
|
|
addBlockStatementFirstComment(node, comment);
|
|
} else {
|
|
addLeadingComment(node, comment);
|
|
}
|
|
}
|
|
|
|
// There are often comments before the else clause of if statements like
|
|
//
|
|
// if (1) { ... }
|
|
// // comment
|
|
// else { ... }
|
|
//
|
|
// They are being attached as leading comments of the BlockExpression which
|
|
// is not well printed. What we want is to instead move the comment inside
|
|
// of the block and make it leadingComment of the first element of the block
|
|
// or dangling comment of the block if there is nothing inside
|
|
//
|
|
// if (1) { ... }
|
|
// else {
|
|
// // comment
|
|
// ...
|
|
// }
|
|
function handleIfStatementComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) {
|
|
if (
|
|
!enclosingNode ||
|
|
enclosingNode.type !== "IfStatement" ||
|
|
!followingNode
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
// We unfortunately have no way using the AST or location of nodes to know
|
|
// if the comment is positioned before or after the condition parenthesis:
|
|
// if (a /* comment */) {}
|
|
// if (a) /* comment */ {}
|
|
// The only workaround I found is to look at the next character to see if
|
|
// it is a ).
|
|
if (getNextNonSpaceNonCommentCharacter(text, comment) === ")") {
|
|
addTrailingComment(precedingNode, comment);
|
|
return true;
|
|
}
|
|
|
|
if (followingNode.type === "BlockStatement") {
|
|
addBlockStatementFirstComment(followingNode, comment);
|
|
return true;
|
|
}
|
|
|
|
if (followingNode.type === "IfStatement") {
|
|
addBlockOrNotComment(followingNode.consequent, comment);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Same as IfStatement but for TryStatement
|
|
function handleTryStatementComments(enclosingNode, followingNode, comment) {
|
|
if (
|
|
!enclosingNode ||
|
|
enclosingNode.type !== "TryStatement" ||
|
|
!followingNode
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (followingNode.type === "BlockStatement") {
|
|
addBlockStatementFirstComment(followingNode, comment);
|
|
return true;
|
|
}
|
|
|
|
if (followingNode.type === "TryStatement") {
|
|
addBlockOrNotComment(followingNode.finalizer, comment);
|
|
return true;
|
|
}
|
|
|
|
if (followingNode.type === "CatchClause") {
|
|
addBlockOrNotComment(followingNode.body, comment);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
enclosingNode.type === "MemberExpression" &&
|
|
followingNode &&
|
|
followingNode.type === "Identifier"
|
|
) {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function handleConditionalExpressionComments(
|
|
enclosingNode,
|
|
precedingNode,
|
|
followingNode,
|
|
comment,
|
|
text
|
|
) {
|
|
const isSameLineAsPrecedingNode =
|
|
precedingNode &&
|
|
!util.hasNewlineInRange(text, locEnd(precedingNode), locStart(comment));
|
|
|
|
if (
|
|
(!precedingNode || !isSameLineAsPrecedingNode) &&
|
|
enclosingNode &&
|
|
enclosingNode.type === "ConditionalExpression" &&
|
|
followingNode
|
|
) {
|
|
addLeadingComment(followingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "ObjectProperty" ||
|
|
enclosingNode.type === "Property") &&
|
|
enclosingNode.shorthand &&
|
|
enclosingNode.key === precedingNode &&
|
|
enclosingNode.value.type === "AssignmentPattern"
|
|
) {
|
|
addTrailingComment(enclosingNode.value.left, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleCommentInEmptyParens(text, enclosingNode, comment) {
|
|
if (getNextNonSpaceNonCommentCharacter(text, comment) !== ")") {
|
|
return false;
|
|
}
|
|
|
|
// Only add dangling comments to fix the case when no params are present,
|
|
// i.e. a function without any argument.
|
|
if (
|
|
enclosingNode &&
|
|
(((enclosingNode.type === "FunctionDeclaration" ||
|
|
enclosingNode.type === "FunctionExpression" ||
|
|
enclosingNode.type === "ArrowFunctionExpression" ||
|
|
enclosingNode.type === "ClassMethod" ||
|
|
enclosingNode.type === "ObjectMethod") &&
|
|
enclosingNode.params.length === 0) ||
|
|
(enclosingNode.type === "CallExpression" &&
|
|
enclosingNode.arguments.length === 0))
|
|
) {
|
|
addDanglingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "MethodDefinition" &&
|
|
enclosingNode.value.params.length === 0)
|
|
) {
|
|
addDanglingComment(enclosingNode.value, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleLastFunctionArgComments(
|
|
text,
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) {
|
|
// Type definitions functions
|
|
if (
|
|
precedingNode &&
|
|
precedingNode.type === "FunctionTypeParam" &&
|
|
enclosingNode &&
|
|
enclosingNode.type === "FunctionTypeAnnotation" &&
|
|
followingNode &&
|
|
followingNode.type !== "FunctionTypeParam"
|
|
) {
|
|
addTrailingComment(precedingNode, comment);
|
|
return true;
|
|
}
|
|
|
|
// Real functions
|
|
if (
|
|
precedingNode &&
|
|
(precedingNode.type === "Identifier" ||
|
|
precedingNode.type === "AssignmentPattern") &&
|
|
enclosingNode &&
|
|
(enclosingNode.type === "ArrowFunctionExpression" ||
|
|
enclosingNode.type === "FunctionExpression" ||
|
|
enclosingNode.type === "FunctionDeclaration" ||
|
|
enclosingNode.type === "ObjectMethod" ||
|
|
enclosingNode.type === "ClassMethod") &&
|
|
getNextNonSpaceNonCommentCharacter(text, comment) === ")"
|
|
) {
|
|
addTrailingComment(precedingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleClassComments(enclosingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "ClassDeclaration" ||
|
|
enclosingNode.type === "ClassExpression")
|
|
) {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleImportSpecifierComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "ImportSpecifier") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleObjectPropertyComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "ObjectProperty") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleLabeledStatementComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "LabeledStatement") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
enclosingNode.type === "CallExpression" &&
|
|
precedingNode &&
|
|
enclosingNode.callee === precedingNode &&
|
|
enclosingNode.arguments.length > 0
|
|
) {
|
|
addLeadingComment(enclosingNode.arguments[0], comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleUnionTypeComments(
|
|
precedingNode,
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) {
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "UnionTypeAnnotation" ||
|
|
enclosingNode.type === "TSUnionType")
|
|
) {
|
|
addTrailingComment(precedingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handlePropertyComments(enclosingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "Property" ||
|
|
enclosingNode.type === "ObjectProperty")
|
|
) {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleExportNamedDeclarationComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "ExportNamedDeclaration") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleOnlyComments(enclosingNode, ast, comment, isLastComment) {
|
|
// With Flow the enclosingNode is undefined so use the AST instead.
|
|
if (ast && ast.body && ast.body.length === 0) {
|
|
if (isLastComment) {
|
|
addDanglingComment(ast, comment);
|
|
} else {
|
|
addLeadingComment(ast, comment);
|
|
}
|
|
return true;
|
|
} else if (
|
|
enclosingNode &&
|
|
enclosingNode.type === "Program" &&
|
|
enclosingNode.body.length === 0 &&
|
|
enclosingNode.directives &&
|
|
enclosingNode.directives.length === 0
|
|
) {
|
|
if (isLastComment) {
|
|
addDanglingComment(enclosingNode, comment);
|
|
} else {
|
|
addLeadingComment(enclosingNode, comment);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleForComments(enclosingNode, precedingNode, comment) {
|
|
if (
|
|
enclosingNode &&
|
|
(enclosingNode.type === "ForInStatement" ||
|
|
enclosingNode.type === "ForOfStatement")
|
|
) {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleImportDeclarationComments(
|
|
text,
|
|
enclosingNode,
|
|
precedingNode,
|
|
comment
|
|
) {
|
|
if (
|
|
precedingNode &&
|
|
enclosingNode &&
|
|
enclosingNode.type === "ImportDeclaration" &&
|
|
util.hasNewline(text, util.locEnd(comment))
|
|
) {
|
|
addTrailingComment(precedingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleAssignmentPatternComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "AssignmentPattern") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleClassMethodComments(enclosingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "ClassMethod") {
|
|
addTrailingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleTypeAliasComments(enclosingNode, followingNode, comment) {
|
|
if (enclosingNode && enclosingNode.type === "TypeAlias") {
|
|
addLeadingComment(enclosingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleVariableDeclaratorComments(
|
|
enclosingNode,
|
|
followingNode,
|
|
comment
|
|
) {
|
|
if (
|
|
enclosingNode &&
|
|
enclosingNode.type === "VariableDeclarator" &&
|
|
followingNode &&
|
|
(followingNode.type === "ObjectExpression" ||
|
|
followingNode.type === "ArrayExpression")
|
|
) {
|
|
addLeadingComment(followingNode, comment);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function printComment(commentPath, options) {
|
|
const comment = commentPath.getValue();
|
|
comment.printed = true;
|
|
|
|
switch (comment.type) {
|
|
case "CommentBlock":
|
|
case "Block":
|
|
return "/*" + comment.value + "*/";
|
|
case "CommentLine":
|
|
case "Line":
|
|
// Don't print the shebang, it's taken care of in index.js
|
|
if (options.originalText.slice(util.locStart(comment)).startsWith("#!")) {
|
|
return "";
|
|
}
|
|
return "//" + comment.value;
|
|
default:
|
|
throw new Error("Not a comment: " + JSON.stringify(comment));
|
|
}
|
|
}
|
|
|
|
function findExpressionIndexForComment(quasis, comment) {
|
|
const startPos = locStart(comment) - 1;
|
|
|
|
for (let i = 1; i < quasis.length; ++i) {
|
|
if (startPos < getQuasiRange(quasis[i]).start) {
|
|
return i - 1;
|
|
}
|
|
}
|
|
|
|
// We haven't found it, it probably means that some of the locations are off.
|
|
// Let's just return the first one.
|
|
return 0;
|
|
}
|
|
|
|
function getQuasiRange(expr) {
|
|
if (expr.start !== undefined) {
|
|
// Babylon
|
|
return { start: expr.start, end: expr.end };
|
|
}
|
|
// Flow
|
|
return { start: expr.range[0], end: expr.range[1] };
|
|
}
|
|
|
|
function printLeadingComment(commentPath, print, options) {
|
|
const comment = commentPath.getValue();
|
|
const contents = printComment(commentPath, options);
|
|
if (!contents) {
|
|
return "";
|
|
}
|
|
const isBlock = util.isBlockComment(comment);
|
|
|
|
// Leading block comments should see if they need to stay on the
|
|
// same line or not.
|
|
if (isBlock) {
|
|
return concat([
|
|
contents,
|
|
util.hasNewline(options.originalText, locEnd(comment)) ? hardline : " "
|
|
]);
|
|
}
|
|
|
|
return concat([contents, hardline]);
|
|
}
|
|
|
|
function printTrailingComment(commentPath, print, options) {
|
|
const comment = commentPath.getValue();
|
|
const contents = printComment(commentPath, options);
|
|
if (!contents) {
|
|
return "";
|
|
}
|
|
const isBlock = util.isBlockComment(comment);
|
|
|
|
if (
|
|
util.hasNewline(options.originalText, locStart(comment), {
|
|
backwards: true
|
|
})
|
|
) {
|
|
// This allows comments at the end of nested structures:
|
|
// {
|
|
// x: 1,
|
|
// y: 2
|
|
// // A comment
|
|
// }
|
|
// Those kinds of comments are almost always leading comments, but
|
|
// here it doesn't go "outside" the block and turns it into a
|
|
// trailing comment for `2`. We can simulate the above by checking
|
|
// if this a comment on its own line; normal trailing comments are
|
|
// always at the end of another expression.
|
|
|
|
const isLineBeforeEmpty = util.isPreviousLineEmpty(
|
|
options.originalText,
|
|
comment
|
|
);
|
|
|
|
return lineSuffix(
|
|
concat([hardline, isLineBeforeEmpty ? hardline : "", contents])
|
|
);
|
|
} else if (isBlock) {
|
|
// Trailing block comments never need a newline
|
|
return concat([" ", contents]);
|
|
}
|
|
|
|
return concat([lineSuffix(" " + contents), !isBlock ? breakParent : ""]);
|
|
}
|
|
|
|
function printDanglingComments(path, options, sameIndent) {
|
|
const parts = [];
|
|
const node = path.getValue();
|
|
|
|
if (!node || !node.comments) {
|
|
return "";
|
|
}
|
|
|
|
path.each(commentPath => {
|
|
const comment = commentPath.getValue();
|
|
if (comment && !comment.leading && !comment.trailing) {
|
|
parts.push(printComment(commentPath, options));
|
|
}
|
|
}, "comments");
|
|
|
|
if (parts.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
if (sameIndent) {
|
|
return join(hardline, parts);
|
|
}
|
|
return indent(concat([hardline, join(hardline, parts)]));
|
|
}
|
|
|
|
function printComments(path, print, options, needsSemi) {
|
|
const value = path.getValue();
|
|
const printed = print(path);
|
|
const comments = value && value.comments;
|
|
|
|
if (!comments || comments.length === 0) {
|
|
return printed;
|
|
}
|
|
|
|
const leadingParts = [];
|
|
const trailingParts = [needsSemi ? ";" : "", printed];
|
|
|
|
path.each(commentPath => {
|
|
const comment = commentPath.getValue();
|
|
const leading = comment.leading;
|
|
const trailing = comment.trailing;
|
|
|
|
if (leading) {
|
|
const contents = printLeadingComment(commentPath, print, options);
|
|
if (!contents) {
|
|
return;
|
|
}
|
|
leadingParts.push(contents);
|
|
|
|
const text = options.originalText;
|
|
if (util.hasNewline(text, util.skipNewline(text, util.locEnd(comment)))) {
|
|
leadingParts.push(hardline);
|
|
}
|
|
} else if (trailing) {
|
|
trailingParts.push(printTrailingComment(commentPath, print, options));
|
|
}
|
|
}, "comments");
|
|
|
|
return concat(leadingParts.concat(trailingParts));
|
|
}
|
|
|
|
module.exports = {
|
|
attach,
|
|
printComments,
|
|
printDanglingComments,
|
|
getSortedChildNodes
|
|
};
|