tolkfmt-test-dev
Version:
Code formatter for the Tolk programming language
228 lines • 7.94 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.bindComments = bindComments;
exports.getLeading = getLeading;
exports.getTrailing = getTrailing;
exports.takeLeading = takeLeading;
exports.takeTrailing = takeTrailing;
exports.takeDangling = takeDangling;
function isReallyTrailing(node) {
const { nextSibling } = node;
if (nextSibling?.type === "comment") {
// simple case
return true;
}
const nextNextSibling = nextSibling?.nextSibling;
if (nextSibling?.type === ",") {
if (node.type === "call_argument") {
// cannot step over comma in `foo(...)`
return false;
}
}
if (nextNextSibling?.type === "comment") {
return true;
}
// likely not a trailing
return false;
}
function bindComments(root) {
const comments = collectComments(root);
const byNode = new Map();
const nodes = collectNamedNodes(root);
// console.error("nodes:", nodes.map(it => "`" + it.text + `\` (${it.type})`).join("\n"))
let index = 0;
outer: while (index < comments.length) {
for (const node of nodes) {
while (index < comments.length && comments[index].end <= node.startIndex) {
attachLeading(comments[index++], node, byNode);
continue outer;
}
const lastTok = lastTokenRow(node);
while (index < comments.length &&
comments[index].start >= lastTok.endIndex &&
comments[index].startRow === lastTok.endPosition.row &&
isReallyTrailing(node)) {
attachTrailing(comments[index++], node, byNode);
continue outer;
}
while (index < comments.length &&
comments[index].start > node.startIndex &&
comments[index].end < node.endIndex &&
!isInsideChild(comments[index].node, node)) {
attachDangling(comments[index++], node, byNode);
continue outer;
}
// Special handling for block_statement: comments inside empty blocks or between statements
if (node.type === "block_statement" &&
index < comments.length &&
comments[index].start > node.startIndex &&
comments[index].end < node.endIndex) {
const comment = comments[index];
const statements = node.namedChildren.filter(child => child !== null && child.type !== "comment");
// If block has no statements, comment should be dangling
if (statements.length === 0) {
attachDangling(comments[index++], node, byNode);
continue outer;
}
// Check if comment is between statements or not attached to any statement
let attachedToStatement = false;
for (const stmt of statements) {
if (!stmt)
continue;
if (comment.end <= stmt.startIndex) {
// Comment is before this statement, should be leading to statement
attachedToStatement = true;
break;
}
if (comment.start >= stmt.endIndex) {
// Check if comment is on the same line as statement end - should be trailing
if (comment.startRow === stmt.endPosition.row) {
attachedToStatement = true;
break;
}
// Comment might be after this statement, continue checking
continue;
}
if (comment.start >= stmt.startIndex && comment.end <= stmt.endIndex) {
// Comment is inside statement, let normal logic handle it
attachedToStatement = true;
break;
}
}
if (!attachedToStatement) {
attachDangling(comments[index++], node, byNode);
continue outer;
}
}
}
const comment = comments[index];
const parent = parentOfType(comment.node, "block_statement", "source_file");
if (parent) {
attachDangling(comment, parent, byNode);
}
// TODO: warn, we cannot attach comment!
index++;
}
while (index < comments.length) {
attachTrailing(comments[index++], root, byNode);
}
return byNode;
}
function lastTokenRow(node) {
let cur = node;
while (cur !== undefined && cur.namedChildCount > 0) {
cur = cur.namedChildren[cur.namedChildCount - 1] ?? undefined;
}
return cur ?? node;
}
function getLeading(node, comments) {
const entry = comments.get(node.id);
if (!entry)
return [];
return entry.leading;
}
function getTrailing(node, comments) {
const entry = comments.get(node.id);
if (!entry)
return [];
return entry.trailing;
}
function takeLeading(node, comments) {
const entry = comments.get(node.id);
if (!entry)
return [];
const out = entry.leading;
entry.leading = [];
return out;
}
function takeTrailing(node, comments) {
const entry = comments.get(node.id);
if (!entry)
return [];
const out = entry.trailing;
entry.trailing = [];
return out;
}
function takeDangling(node, comments) {
const entry = comments.get(node.id);
if (!entry)
return [];
const out = entry.dangling;
entry.dangling = [];
return out;
}
function dfs(n, cb) {
cb(n);
for (const child of n.namedChildren) {
if (!child)
continue;
dfs(child, cb);
}
}
function collectComments(root) {
const out = [];
dfs(root, n => {
if (n.type === "comment") {
out.push({
node: n,
text: n.text,
start: n.startIndex,
end: n.endIndex,
startRow: n.startPosition.row,
endRow: n.endPosition.row,
});
}
});
return out.sort((a, b) => a.start - b.start);
}
function collectNamedNodes(root) {
const res = [];
dfs(root, n => {
if (n.isNamed && n.type !== "comment")
res.push(n);
});
return res.sort((a, b) => a.startIndex - b.startIndex);
}
function ensureEntry(n, map) {
let e = map.get(n.id);
if (!e) {
e = { leading: [], trailing: [], dangling: [] };
map.set(n.id, e);
}
return e;
}
function attachLeading(c, node, map) {
// console.error(`attach leading ${c.text} to ${node.text}, id: ${node.id}, type: ${node.type}`)
ensureEntry(node, map).leading.push(c);
}
function attachTrailing(c, node, map) {
// console.error(`attach trailing ${c.text} to ${node.text}, id: ${node.id}, type: ${node.type}`)
ensureEntry(node, map).trailing.push(c);
}
function attachDangling(c, node, map) {
// console.error(`attach dangling ${c.text} to ${node.text}, id: ${node.id}, type: ${node.type}`)
ensureEntry(node, map).dangling.push(c);
}
function isInsideChild(comment, parent) {
for (const child of parent.namedChildren) {
if (!child)
continue;
if (child.startIndex <= comment.startIndex && child.endIndex >= comment.endIndex) {
return true;
}
}
return false;
}
function parentOfType(node, ...types) {
let { parent } = node;
for (let i = 0; i < 100; i++) {
if (parent === null)
return undefined;
if (types.includes(parent.type))
return parent;
const { parent: newParent } = parent;
parent = newParent;
}
return undefined;
}
//# sourceMappingURL=comments.js.map