Skip to content

Commit 4ae5421

Browse files
committed
feat: ensure text before and after list of terms are separate paragraphs
1 parent 2bd490d commit 4ae5421

File tree

5 files changed

+1304
-17
lines changed

5 files changed

+1304
-17
lines changed

src/typstToTextlintAst.ts

Lines changed: 125 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,84 @@ export const paragraphizeTextlintAstObject = (
924924
return { nodes: collected, nextIndex: i };
925925
};
926926

927+
const splitParagraphTermlist = (paraNode: AstNode): Content[] => {
928+
if (!hasChildren(paraNode)) return [];
929+
const isBlankStr = (c: Content): boolean =>
930+
c.type === ASTNodeTypes.Str && c.raw?.trim() === "";
931+
const isTermItem = (c: Content): boolean =>
932+
isAstNode(c) && isTypstType(c.type, /^Marked::TermItem$/);
933+
const createParagraph = (nodes: Content[]): Content => {
934+
const first = nodes[0];
935+
const last = nodes[nodes.length - 1];
936+
return {
937+
type: ASTNodeTypes.Paragraph,
938+
children: nodes,
939+
loc: { start: first.loc.start, end: last.loc.end },
940+
range: [first.range[0], last.range[1]],
941+
raw: nodes.map((c) => c.raw).join(""),
942+
} as Content;
943+
};
944+
945+
const segments: { isTerm: boolean; nodes: Content[] }[] = [];
946+
let current: { isTerm: boolean; nodes: Content[] } | null = null;
947+
for (const child of paraNode.children) {
948+
if (isBlankStr(child)) {
949+
continue;
950+
}
951+
const isTerm = isTermItem(child);
952+
if (!current || current.isTerm !== isTerm) {
953+
if (current) segments.push(current);
954+
current = { isTerm, nodes: [child] };
955+
} else {
956+
current.nodes.push(child);
957+
}
958+
}
959+
if (current) segments.push(current);
960+
961+
const results: Content[] = [];
962+
for (const seg of segments) {
963+
if (seg.isTerm) {
964+
results.push(...seg.nodes);
965+
continue;
966+
}
967+
968+
const nodes = seg.nodes.flatMap(flattenTypstMarkupChildren);
969+
if (nodes.length === 0) continue;
970+
results.push(createParagraph(nodes));
971+
}
972+
return results;
973+
};
974+
975+
const collectConsecutiveRootTermItems = (
976+
arr: Content[],
977+
startIndex: number,
978+
): { termItems: Content[]; nextIndex: number } => {
979+
const termItems: Content[] = [];
980+
let i = startIndex;
981+
while (i < arr.length) {
982+
const currentNode = arr[i];
983+
if (
984+
isAstNode(currentNode) &&
985+
isTypstType(currentNode.type, /^Marked::TermItem$/)
986+
) {
987+
termItems.push(currentNode);
988+
i++;
989+
continue;
990+
}
991+
if (currentNode.type === ASTNodeTypes.Str && currentNode.raw === "\n") {
992+
if (
993+
i + 1 < arr.length &&
994+
isAstNode(arr[i + 1]) &&
995+
isTypstType(arr[i + 1].type, /^Marked::TermItem$/)
996+
) {
997+
i++;
998+
continue;
999+
}
1000+
}
1001+
break;
1002+
}
1003+
return { termItems, nextIndex: i };
1004+
};
9271005
const sourceChildren = rootNode.children;
9281006

9291007
const children: Content[] = [];
@@ -939,6 +1017,16 @@ export const paragraphizeTextlintAstObject = (
9391017

9401018
const node = sourceChildren[i];
9411019

1020+
if (isAstNode(node) && isTypstType(node.type, /^Marked::TermItem$/)) {
1021+
const { termItems, nextIndex } = collectConsecutiveRootTermItems(
1022+
sourceChildren,
1023+
i,
1024+
);
1025+
children.push(...termItems);
1026+
i = nextIndex;
1027+
continue;
1028+
}
1029+
9421030
// Collect consecutive ListItems into a single List node.
9431031
if (node.type === ASTNodeTypes.ListItem) {
9441032
const listItems: Content[] = [node];
@@ -1144,7 +1232,6 @@ export const paragraphizeTextlintAstObject = (
11441232
paragraph.push(node);
11451233
i++;
11461234

1147-
// Collect consecutive nodes for paragraph grouping.
11481235
while (i < sourceChildren.length) {
11491236
const currentNode = sourceChildren[i];
11501237

@@ -1164,23 +1251,48 @@ export const paragraphizeTextlintAstObject = (
11641251
if (paragraph.length > 0) {
11651252
const headNode = paragraph[0];
11661253
const lastNode = paragraph[paragraph.length - 1];
1167-
1168-
// Special handling for hash symbols.
11691254
if (
11701255
["Kw::Hash", "Fn::(Hash: &quot;#&quot;)"].includes(headNode.type)
11711256
) {
11721257
children.push(...paragraph);
11731258
} else {
1174-
children.push({
1175-
loc: {
1176-
start: headNode.loc.start,
1177-
end: lastNode.loc.end,
1178-
},
1179-
range: [headNode.range[0], lastNode.range[1]],
1180-
raw: paragraph.map((node) => node.raw).join(""),
1181-
type: ASTNodeTypes.Paragraph,
1182-
children: paragraph,
1183-
} as Content);
1259+
const isTermItem = (c: Content): boolean =>
1260+
isAstNode(c) && isTypstType(c.type, /^Marked::TermItem$/);
1261+
const createParagraph = (nodes: Content[]): Content => {
1262+
const first = nodes[0];
1263+
const last = nodes[nodes.length - 1];
1264+
return {
1265+
type: ASTNodeTypes.Paragraph,
1266+
children: nodes,
1267+
loc: { start: first.loc.start, end: last.loc.end },
1268+
range: [first.range[0], last.range[1]],
1269+
raw: nodes.map((n) => n.raw).join(""),
1270+
} as Content;
1271+
};
1272+
const innerParagraphWithTerms: AstNode | undefined = (() => {
1273+
if (paragraph.length !== 1) return undefined;
1274+
const only = paragraph[0];
1275+
if (only.type !== ASTNodeTypes.Paragraph) return undefined;
1276+
if (!isAstNode(only) || !hasChildren(only)) return undefined;
1277+
if (!only.children.some(isTermItem)) return undefined;
1278+
return only;
1279+
})();
1280+
if (innerParagraphWithTerms) {
1281+
children.push(...splitParagraphTermlist(innerParagraphWithTerms));
1282+
continue;
1283+
}
1284+
if (paragraph.some(isTermItem)) {
1285+
const paraNode: AstNode = {
1286+
type: ASTNodeTypes.Paragraph,
1287+
children: paragraph as Content[],
1288+
loc: { start: headNode.loc.start, end: lastNode.loc.end },
1289+
range: [headNode.range[0], lastNode.range[1]],
1290+
raw: paragraph.map((p: Content) => p.raw).join(""),
1291+
};
1292+
children.push(...splitParagraphTermlist(paraNode));
1293+
continue;
1294+
}
1295+
children.push(createParagraph(paragraph));
11841296
}
11851297
}
11861298
}

test/unit/fixtures/List/input.typ

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@
55
1. x
66
2. y
77
3. z
8+
9+
/ Ligature: A merged glyph.
10+
/ Kerning: A spacing adjustment
11+
between two adjacent letters.

0 commit comments

Comments
 (0)