yaml-unist-parser
Version:
A YAML parser that produces output compatible with unist
174 lines (173 loc) • 6.73 kB
JavaScript
import { defineParents } from "./utils/define-parents.js";
import { getPointText } from "./utils/get-point-text.js";
export function attachComments(root) {
defineParents(root);
const nodeTable = createNodeTable(root);
const restDocuments = root.children.slice();
root.comments
.sort((a, b) => a.position.start.offset - b.position.end.offset)
.filter(comment => !comment._parent)
.forEach(comment => {
while (restDocuments.length > 1 &&
comment.position.start.line > restDocuments[0].position.end.line) {
restDocuments.shift();
}
attachComment(comment, nodeTable, restDocuments[0]);
});
}
function createNodeTable(root) {
const nodeTable = Array.from(new Array(root.position.end.line), () => ({}));
for (const comment of root.comments) {
nodeTable[comment.position.start.line - 1].comment = comment;
}
initNodeTable(nodeTable, root);
return nodeTable;
}
function initNodeTable(nodeTable, node) {
// empty mappingKey/mappingValue
if (node.position.start.offset === node.position.end.offset) {
return;
}
if ("leadingComments" in node) {
const { start } = node.position;
const { leadingAttachableNode } = nodeTable[start.line - 1];
if (!leadingAttachableNode ||
start.column < leadingAttachableNode.position.start.column) {
nodeTable[start.line - 1].leadingAttachableNode = node;
}
}
if ("trailingComment" in node &&
node.position.end.column > 1 &&
node.type !== "document" &&
node.type !== "documentHead") {
const { end } = node.position;
const { trailingAttachableNode } = nodeTable[end.line - 1];
if (!trailingAttachableNode ||
end.column >= trailingAttachableNode.position.end.column) {
nodeTable[end.line - 1].trailingAttachableNode = node;
}
}
if (node.type !== "root" &&
node.type !== "document" &&
node.type !== "documentHead" &&
node.type !== "documentBody") {
const { start, end } = node.position;
const lines = [end.line].concat(start.line === end.line ? [] : start.line);
for (const line of lines) {
const currentEndNode = nodeTable[line - 1].trailingNode;
if (!currentEndNode || end.column >= currentEndNode.position.end.column) {
nodeTable[line - 1].trailingNode = node;
}
}
}
if ("children" in node) {
node.children.forEach(child => {
initNodeTable(nodeTable, child);
});
}
}
function attachComment(comment, nodeTable, document) {
const commentLine = comment.position.start.line;
const { trailingAttachableNode } = nodeTable[commentLine - 1];
if (trailingAttachableNode) {
// istanbul ignore next
if (trailingAttachableNode.trailingComment) {
throw new Error(`Unexpected multiple trailing comment at ${getPointText(comment.position.start)}`);
}
defineParents(comment, trailingAttachableNode);
trailingAttachableNode.trailingComment = comment;
return;
}
for (let line = commentLine; line >= document.position.start.line; line--) {
const { trailingNode } = nodeTable[line - 1];
let currentNode;
if (!trailingNode) {
/**
* a:
* b:
* #b
* #a
*
* a:
* b:
* #a
* #a
*/
if (line !== commentLine && nodeTable[line - 1].comment) {
currentNode = nodeTable[line - 1].comment._parent;
}
else {
continue;
}
}
else {
currentNode = trailingNode;
}
if (currentNode.type === "sequence" || currentNode.type === "mapping") {
currentNode = currentNode.children[0];
}
if (currentNode.type === "mappingItem") {
const [mappingKey, mappingValue] = currentNode.children;
currentNode = isExplicitMappingKey(mappingKey)
? mappingKey
: mappingValue;
}
while (true) {
if (shouldOwnEndComment(currentNode, comment)) {
defineParents(comment, currentNode);
currentNode.endComments.push(comment);
return;
}
if (!currentNode._parent) {
break;
}
currentNode = currentNode._parent;
}
break;
}
for (let line = commentLine + 1; line <= document.position.end.line; line++) {
const { leadingAttachableNode } = nodeTable[line - 1];
if (leadingAttachableNode) {
defineParents(comment, leadingAttachableNode);
leadingAttachableNode.leadingComments.push(comment);
return;
}
}
const documentBody = document.children[1];
defineParents(comment, documentBody);
documentBody.endComments.push(comment);
}
function shouldOwnEndComment(node, comment) {
if (node.position.start.offset < comment.position.start.offset &&
node.position.end.offset > comment.position.end.offset) {
switch (node.type) {
case "flowMapping":
case "flowSequence":
return (node.children.length === 0 ||
comment.position.start.line >
node.children[node.children.length - 1].position.end.line);
}
}
if (comment.position.end.offset < node.position.end.offset) {
return false;
}
switch (node.type) {
case "sequenceItem":
return comment.position.start.column > node.position.start.column;
case "mappingKey":
case "mappingValue":
return (comment.position.start.column > node._parent.position.start.column &&
(node.children.length === 0 ||
(node.children.length === 1 &&
node.children[0].type !== "blockFolded" &&
node.children[0].type !== "blockLiteral")) &&
(node.type === "mappingValue" || isExplicitMappingKey(node)));
default:
return false;
}
}
function isExplicitMappingKey(node) {
return (node.position.start !== node.position.end &&
(node.children.length === 0 ||
node.position.start.offset !== node.children[0].position.start.offset));
}