@wdprlib/parser 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/index.cjs +295 -118
  2. package/dist/index.js +272 -95
  3. package/package.json +5 -3
  4. package/src/index.ts +163 -0
  5. package/src/lexer/index.ts +20 -0
  6. package/src/lexer/lexer.ts +687 -0
  7. package/src/lexer/tokens.ts +141 -0
  8. package/src/parser/constants.ts +173 -0
  9. package/src/parser/depth.ts +251 -0
  10. package/src/parser/index.ts +18 -0
  11. package/src/parser/parse.ts +315 -0
  12. package/src/parser/postprocess/divAdjacentParagraph.ts +76 -0
  13. package/src/parser/postprocess/index.ts +15 -0
  14. package/src/parser/postprocess/spanStrip.ts +697 -0
  15. package/src/parser/preprocess/expr.ts +265 -0
  16. package/src/parser/preprocess/index.ts +38 -0
  17. package/src/parser/preprocess/typography.ts +67 -0
  18. package/src/parser/preprocess/utils.ts +250 -0
  19. package/src/parser/preprocess/whitespace.ts +111 -0
  20. package/src/parser/rules/block/align.ts +282 -0
  21. package/src/parser/rules/block/bibliography.ts +359 -0
  22. package/src/parser/rules/block/block-list.ts +689 -0
  23. package/src/parser/rules/block/blockquote.ts +238 -0
  24. package/src/parser/rules/block/center.ts +87 -0
  25. package/src/parser/rules/block/clear-float.ts +75 -0
  26. package/src/parser/rules/block/code.ts +187 -0
  27. package/src/parser/rules/block/collapsible.ts +337 -0
  28. package/src/parser/rules/block/comment.ts +73 -0
  29. package/src/parser/rules/block/content-separator.ts +79 -0
  30. package/src/parser/rules/block/definition-list.ts +270 -0
  31. package/src/parser/rules/block/div.ts +400 -0
  32. package/src/parser/rules/block/embed-block.ts +153 -0
  33. package/src/parser/rules/block/footnoteblock.ts +200 -0
  34. package/src/parser/rules/block/heading.ts +142 -0
  35. package/src/parser/rules/block/horizontal-rule.ts +61 -0
  36. package/src/parser/rules/block/html.ts +222 -0
  37. package/src/parser/rules/block/iframe.ts +239 -0
  38. package/src/parser/rules/block/iftags.ts +150 -0
  39. package/src/parser/rules/block/include.ts +179 -0
  40. package/src/parser/rules/block/index.ts +127 -0
  41. package/src/parser/rules/block/list.ts +244 -0
  42. package/src/parser/rules/block/math.ts +183 -0
  43. package/src/parser/rules/block/module/backlinks/index.ts +31 -0
  44. package/src/parser/rules/block/module/backlinks/types.ts +21 -0
  45. package/src/parser/rules/block/module/categories/index.ts +34 -0
  46. package/src/parser/rules/block/module/categories/types.ts +21 -0
  47. package/src/parser/rules/block/module/css/index.ts +37 -0
  48. package/src/parser/rules/block/module/iftags/condition.ts +109 -0
  49. package/src/parser/rules/block/module/iftags/index.ts +26 -0
  50. package/src/parser/rules/block/module/iftags/preprocess.ts +140 -0
  51. package/src/parser/rules/block/module/iftags/resolve.ts +73 -0
  52. package/src/parser/rules/block/module/iftags/types.ts +63 -0
  53. package/src/parser/rules/block/module/include/index.ts +20 -0
  54. package/src/parser/rules/block/module/include/resolve.ts +556 -0
  55. package/src/parser/rules/block/module/index.ts +122 -0
  56. package/src/parser/rules/block/module/join/index.ts +34 -0
  57. package/src/parser/rules/block/module/join/types.ts +23 -0
  58. package/src/parser/rules/block/module/listpages/compiler.ts +453 -0
  59. package/src/parser/rules/block/module/listpages/extract.ts +410 -0
  60. package/src/parser/rules/block/module/listpages/index.ts +83 -0
  61. package/src/parser/rules/block/module/listpages/normalize.ts +390 -0
  62. package/src/parser/rules/block/module/listpages/parser.ts +106 -0
  63. package/src/parser/rules/block/module/listpages/resolve.ts +130 -0
  64. package/src/parser/rules/block/module/listpages/types.ts +513 -0
  65. package/src/parser/rules/block/module/listpages/url-resolver.ts +186 -0
  66. package/src/parser/rules/block/module/listusers/compiler.ts +77 -0
  67. package/src/parser/rules/block/module/listusers/extract.ts +45 -0
  68. package/src/parser/rules/block/module/listusers/index.ts +36 -0
  69. package/src/parser/rules/block/module/listusers/parser.ts +54 -0
  70. package/src/parser/rules/block/module/listusers/resolve.ts +58 -0
  71. package/src/parser/rules/block/module/listusers/types.ts +93 -0
  72. package/src/parser/rules/block/module/mapping.ts +61 -0
  73. package/src/parser/rules/block/module/page-tree/index.ts +38 -0
  74. package/src/parser/rules/block/module/page-tree/types.ts +29 -0
  75. package/src/parser/rules/block/module/rate/index.ts +28 -0
  76. package/src/parser/rules/block/module/rate/types.ts +19 -0
  77. package/src/parser/rules/block/module/resolve.ts +411 -0
  78. package/src/parser/rules/block/module/types-common.ts +59 -0
  79. package/src/parser/rules/block/module/types.ts +61 -0
  80. package/src/parser/rules/block/module/utils.ts +43 -0
  81. package/src/parser/rules/block/module/walk.ts +380 -0
  82. package/src/parser/rules/block/module.ts +164 -0
  83. package/src/parser/rules/block/orphan-li.ts +177 -0
  84. package/src/parser/rules/block/paragraph.ts +157 -0
  85. package/src/parser/rules/block/table-block.ts +726 -0
  86. package/src/parser/rules/block/table.ts +441 -0
  87. package/src/parser/rules/block/tabview.ts +331 -0
  88. package/src/parser/rules/block/toc.ts +129 -0
  89. package/src/parser/rules/block/utils.ts +615 -0
  90. package/src/parser/rules/index.ts +49 -0
  91. package/src/parser/rules/inline/anchor-name.ts +154 -0
  92. package/src/parser/rules/inline/anchor.ts +327 -0
  93. package/src/parser/rules/inline/bibcite.ts +153 -0
  94. package/src/parser/rules/inline/bold.ts +86 -0
  95. package/src/parser/rules/inline/color.ts +140 -0
  96. package/src/parser/rules/inline/comment.ts +90 -0
  97. package/src/parser/rules/inline/equation-ref.ts +115 -0
  98. package/src/parser/rules/inline/expr.ts +526 -0
  99. package/src/parser/rules/inline/footnote.ts +223 -0
  100. package/src/parser/rules/inline/guillemet.ts +64 -0
  101. package/src/parser/rules/inline/html.ts +132 -0
  102. package/src/parser/rules/inline/image.ts +328 -0
  103. package/src/parser/rules/inline/index.ts +150 -0
  104. package/src/parser/rules/inline/italic.ts +74 -0
  105. package/src/parser/rules/inline/line-break.ts +326 -0
  106. package/src/parser/rules/inline/link-anchor.ts +147 -0
  107. package/src/parser/rules/inline/link-single.ts +164 -0
  108. package/src/parser/rules/inline/link-star.ts +134 -0
  109. package/src/parser/rules/inline/link-triple.ts +267 -0
  110. package/src/parser/rules/inline/math-inline.ts +126 -0
  111. package/src/parser/rules/inline/monospace.ts +78 -0
  112. package/src/parser/rules/inline/raw.ts +262 -0
  113. package/src/parser/rules/inline/size.ts +244 -0
  114. package/src/parser/rules/inline/span.ts +424 -0
  115. package/src/parser/rules/inline/strikethrough.ts +115 -0
  116. package/src/parser/rules/inline/subscript.ts +84 -0
  117. package/src/parser/rules/inline/superscript.ts +84 -0
  118. package/src/parser/rules/inline/text.ts +84 -0
  119. package/src/parser/rules/inline/underline.ts +127 -0
  120. package/src/parser/rules/inline/user.ts +147 -0
  121. package/src/parser/rules/inline/utils.ts +344 -0
  122. package/src/parser/rules/types.ts +252 -0
  123. package/src/parser/rules/utils.ts +155 -0
  124. package/src/parser/toc.ts +130 -0
@@ -0,0 +1,380 @@
1
+ /**
2
+ *
3
+ * AST element traversal and transformation utilities.
4
+ *
5
+ * Provides shared logic for recursively visiting and transforming child elements
6
+ * across all AST node types that contain nested elements. The AST has several
7
+ * "special" structures (list, table, definition-list, tab-view) that store
8
+ * children in type-specific locations, plus a generic pattern where elements
9
+ * are stored in `data.elements`. These utilities abstract over those differences
10
+ * so callers can focus on their transformation logic.
11
+ *
12
+ * Three main functions are provided:
13
+ * - `walkElements` - Read-only traversal (visitor pattern)
14
+ * - `mapElementChildren` - Stateless transformation of child arrays
15
+ * - `mapElementChildrenWithState` - Stateful transformation with threaded state
16
+ *
17
+ * Used by the ListPages extraction, module resolution, and include resolution systems.
18
+ *
19
+ * @module
20
+ */
21
+
22
+ import type {
23
+ Element,
24
+ ListData,
25
+ ListItem,
26
+ TableData,
27
+ TableRow,
28
+ TableCell,
29
+ DefinitionListItem,
30
+ TabData,
31
+ } from "@wdprlib/ast";
32
+
33
+ /**
34
+ * Walk all elements recursively in depth-first order, calling a callback for each.
35
+ *
36
+ * The callback is invoked for every element in the tree, including elements nested
37
+ * inside lists, tables, definition lists, tab views, and any element with a
38
+ * `data.elements` array. The callback is called before descending into children
39
+ * (pre-order traversal).
40
+ *
41
+ * This is a read-only traversal; the callback cannot modify the tree structure.
42
+ * Use `mapElementChildren` or `mapElementChildrenWithState` for transformations.
43
+ *
44
+ * @param elements - Array of elements to traverse
45
+ * @param callback - Function called for each element encountered
46
+ */
47
+ export function walkElements(elements: Element[], callback: (element: Element) => void): void {
48
+ for (const element of elements) {
49
+ callback(element);
50
+
51
+ // List
52
+ if (element.element === "list") {
53
+ const listData = element.data as ListData;
54
+ for (const item of listData.items) {
55
+ if (item["item-type"] === "elements") {
56
+ walkElements(item.elements, callback);
57
+ } else if (item["item-type"] === "sub-list") {
58
+ walkElements([{ element: "list", data: item.data } as Element], callback);
59
+ }
60
+ }
61
+ continue;
62
+ }
63
+
64
+ // Table
65
+ if (element.element === "table") {
66
+ const tableData = element.data as TableData;
67
+ for (const row of tableData.rows) {
68
+ for (const cell of row.cells) {
69
+ walkElements(cell.elements, callback);
70
+ }
71
+ }
72
+ continue;
73
+ }
74
+
75
+ // Definition list
76
+ if (element.element === "definition-list") {
77
+ const defListData = element.data as DefinitionListItem[];
78
+ for (const item of defListData) {
79
+ walkElements(item.key, callback);
80
+ walkElements(item.value, callback);
81
+ }
82
+ continue;
83
+ }
84
+
85
+ // Tab view
86
+ if (element.element === "tab-view") {
87
+ const tabData = element.data as TabData[];
88
+ for (const tab of tabData) {
89
+ walkElements(tab.elements, callback);
90
+ }
91
+ continue;
92
+ }
93
+
94
+ // Generic elements with data.elements
95
+ if ("data" in element && element.data && typeof element.data === "object") {
96
+ const data = element.data as Record<string, unknown>;
97
+ if ("elements" in data && Array.isArray(data.elements)) {
98
+ walkElements(data.elements as Element[], callback);
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Create a new element with all child element arrays transformed by a function.
106
+ *
107
+ * This is a structural mapper that knows how to find child element arrays in all
108
+ * AST node types (list items, table cells, definition list keys/values, tab panels,
109
+ * and generic `data.elements`). The transform function receives each child array
110
+ * and returns a new array; the original element is not mutated.
111
+ *
112
+ * If the element has no children, it is returned unchanged (same reference).
113
+ *
114
+ * @param element - The element whose children should be transformed
115
+ * @param transform - Function that receives a child element array and returns a transformed array
116
+ * @returns A new element with transformed children, or the original element if it has no children
117
+ */
118
+ export function mapElementChildren(
119
+ element: Element,
120
+ transform: (elements: Element[]) => Element[],
121
+ ): Element {
122
+ // List
123
+ if (element.element === "list") {
124
+ const listData = element.data as ListData;
125
+ const newItems: ListItem[] = [];
126
+
127
+ for (const item of listData.items) {
128
+ if (item["item-type"] === "elements") {
129
+ newItems.push({
130
+ "item-type": "elements",
131
+ attributes: item.attributes,
132
+ elements: transform(item.elements),
133
+ });
134
+ } else if (item["item-type"] === "sub-list") {
135
+ const subListResult = transform([{ element: "list", data: item.data } as Element]);
136
+ const resolvedList = subListResult[0];
137
+ if (resolvedList?.element === "list") {
138
+ newItems.push({
139
+ "item-type": "sub-list",
140
+ element: "list",
141
+ data: resolvedList.data as ListData,
142
+ });
143
+ } else {
144
+ newItems.push(item);
145
+ }
146
+ }
147
+ }
148
+
149
+ return {
150
+ element: "list",
151
+ data: { ...listData, items: newItems },
152
+ } as Element;
153
+ }
154
+
155
+ // Table
156
+ if (element.element === "table") {
157
+ const tableData = element.data as TableData;
158
+ const newRows: TableRow[] = [];
159
+
160
+ for (const row of tableData.rows) {
161
+ const newCells: TableCell[] = [];
162
+ for (const cell of row.cells) {
163
+ newCells.push({ ...cell, elements: transform(cell.elements) });
164
+ }
165
+ newRows.push({ ...row, cells: newCells });
166
+ }
167
+
168
+ return {
169
+ element: "table",
170
+ data: { ...tableData, rows: newRows },
171
+ } as Element;
172
+ }
173
+
174
+ // Definition list
175
+ if (element.element === "definition-list") {
176
+ const defListData = element.data as DefinitionListItem[];
177
+ const newItems: DefinitionListItem[] = [];
178
+
179
+ for (const item of defListData) {
180
+ newItems.push({
181
+ key_string: item.key_string,
182
+ key: transform(item.key),
183
+ value: transform(item.value),
184
+ });
185
+ }
186
+
187
+ return {
188
+ element: "definition-list",
189
+ data: newItems,
190
+ } as Element;
191
+ }
192
+
193
+ // Tab view
194
+ if (element.element === "tab-view") {
195
+ const tabData = element.data as TabData[];
196
+ const newTabs: TabData[] = [];
197
+
198
+ for (const tab of tabData) {
199
+ newTabs.push({ ...tab, elements: transform(tab.elements) });
200
+ }
201
+
202
+ return {
203
+ element: "tab-view",
204
+ data: newTabs,
205
+ } as Element;
206
+ }
207
+
208
+ // Generic elements with data.elements
209
+ if ("data" in element && element.data && typeof element.data === "object") {
210
+ const data = element.data as Record<string, unknown>;
211
+ if ("elements" in data && Array.isArray(data.elements)) {
212
+ return {
213
+ ...element,
214
+ data: {
215
+ ...data,
216
+ elements: transform(data.elements as Element[]),
217
+ },
218
+ } as Element;
219
+ }
220
+ }
221
+
222
+ // No children
223
+ return element;
224
+ }
225
+
226
+ /**
227
+ * Create a new element with all child arrays transformed by a stateful function.
228
+ *
229
+ * Like `mapElementChildren`, but the transform function also receives and returns
230
+ * a state value. State is threaded sequentially through each child group: the output
231
+ * state from one group becomes the input state for the next. This is useful when
232
+ * the transformation needs to track information across sibling groups, such as
233
+ * maintaining a monotonically increasing ID counter.
234
+ *
235
+ * @typeParam S - The type of the threaded state
236
+ * @param element - The element whose children should be transformed
237
+ * @param state - Initial state value
238
+ * @param transform - Function that receives a child array and current state,
239
+ * returning transformed elements and updated state
240
+ * @returns Object with the new element and final state value
241
+ */
242
+ export function mapElementChildrenWithState<S>(
243
+ element: Element,
244
+ state: S,
245
+ transform: (elements: Element[], state: S) => { elements: Element[]; state: S },
246
+ ): { element: Element; state: S } {
247
+ // List
248
+ if (element.element === "list") {
249
+ const listData = element.data as ListData;
250
+ const newItems: ListItem[] = [];
251
+ let currentState = state;
252
+
253
+ for (const item of listData.items) {
254
+ if (item["item-type"] === "elements") {
255
+ const result = transform(item.elements, currentState);
256
+ newItems.push({
257
+ "item-type": "elements",
258
+ attributes: item.attributes,
259
+ elements: result.elements,
260
+ });
261
+ currentState = result.state;
262
+ } else if (item["item-type"] === "sub-list") {
263
+ const result = transform([{ element: "list", data: item.data } as Element], currentState);
264
+ const resolvedList = result.elements[0];
265
+ if (resolvedList?.element === "list") {
266
+ newItems.push({
267
+ "item-type": "sub-list",
268
+ element: "list",
269
+ data: resolvedList.data as ListData,
270
+ });
271
+ } else {
272
+ newItems.push(item);
273
+ }
274
+ currentState = result.state;
275
+ }
276
+ }
277
+
278
+ return {
279
+ element: {
280
+ element: "list",
281
+ data: { ...listData, items: newItems },
282
+ } as Element,
283
+ state: currentState,
284
+ };
285
+ }
286
+
287
+ // Table
288
+ if (element.element === "table") {
289
+ const tableData = element.data as TableData;
290
+ const newRows: TableRow[] = [];
291
+ let currentState = state;
292
+
293
+ for (const row of tableData.rows) {
294
+ const newCells: TableCell[] = [];
295
+ for (const cell of row.cells) {
296
+ const result = transform(cell.elements, currentState);
297
+ newCells.push({ ...cell, elements: result.elements });
298
+ currentState = result.state;
299
+ }
300
+ newRows.push({ ...row, cells: newCells });
301
+ }
302
+
303
+ return {
304
+ element: {
305
+ element: "table",
306
+ data: { ...tableData, rows: newRows },
307
+ } as Element,
308
+ state: currentState,
309
+ };
310
+ }
311
+
312
+ // Definition list
313
+ if (element.element === "definition-list") {
314
+ const defListData = element.data as DefinitionListItem[];
315
+ const newItems: DefinitionListItem[] = [];
316
+ let currentState = state;
317
+
318
+ for (const item of defListData) {
319
+ const keyResult = transform(item.key, currentState);
320
+ currentState = keyResult.state;
321
+ const valueResult = transform(item.value, currentState);
322
+ currentState = valueResult.state;
323
+ newItems.push({
324
+ key_string: item.key_string,
325
+ key: keyResult.elements,
326
+ value: valueResult.elements,
327
+ });
328
+ }
329
+
330
+ return {
331
+ element: {
332
+ element: "definition-list",
333
+ data: newItems,
334
+ } as Element,
335
+ state: currentState,
336
+ };
337
+ }
338
+
339
+ // Tab view
340
+ if (element.element === "tab-view") {
341
+ const tabData = element.data as TabData[];
342
+ const newTabs: TabData[] = [];
343
+ let currentState = state;
344
+
345
+ for (const tab of tabData) {
346
+ const result = transform(tab.elements, currentState);
347
+ newTabs.push({ ...tab, elements: result.elements });
348
+ currentState = result.state;
349
+ }
350
+
351
+ return {
352
+ element: {
353
+ element: "tab-view",
354
+ data: newTabs,
355
+ } as Element,
356
+ state: currentState,
357
+ };
358
+ }
359
+
360
+ // Generic elements with data.elements
361
+ if ("data" in element && element.data && typeof element.data === "object") {
362
+ const data = element.data as Record<string, unknown>;
363
+ if ("elements" in data && Array.isArray(data.elements)) {
364
+ const result = transform(data.elements as Element[], state);
365
+ return {
366
+ element: {
367
+ ...element,
368
+ data: {
369
+ ...data,
370
+ elements: result.elements,
371
+ },
372
+ } as Element,
373
+ state: result.state,
374
+ };
375
+ }
376
+ }
377
+
378
+ // No children
379
+ return { element, state };
380
+ }
@@ -0,0 +1,164 @@
1
+ import type { Element } from "@wdprlib/ast";
2
+ import type { BlockRule, ParseContext, RuleResult } from "../types";
3
+ import { currentToken } from "../types";
4
+ import { parseBlockName } from "../utils";
5
+ import { parseAttributesRaw } from "./utils";
6
+ import { getModuleRuleByName } from "./module/mapping";
7
+
8
+ export const moduleRule: BlockRule = {
9
+ name: "module",
10
+ startTokens: ["BLOCK_OPEN"],
11
+ requiresLineStart: false,
12
+
13
+ parse(ctx: ParseContext): RuleResult<Element> {
14
+ const openToken = currentToken(ctx);
15
+ if (openToken.type !== "BLOCK_OPEN") {
16
+ return { success: false };
17
+ }
18
+
19
+ let pos = ctx.pos + 1;
20
+ let consumed = 1;
21
+
22
+ const nameResult = parseBlockName(ctx, pos);
23
+ if (!nameResult || (nameResult.name !== "module" && nameResult.name !== "module654")) {
24
+ return { success: false };
25
+ }
26
+
27
+ // Page syntax disabled (e.g., forum-post mode)
28
+ if (!ctx.settings.enablePageSyntax) {
29
+ return { success: false };
30
+ }
31
+
32
+ pos += nameResult.consumed;
33
+ consumed += nameResult.consumed;
34
+
35
+ // Skip whitespace
36
+ while (ctx.tokens[pos]?.type === "WHITESPACE") {
37
+ pos++;
38
+ consumed++;
39
+ }
40
+
41
+ // Get module name (next TEXT or IDENTIFIER token)
42
+ let moduleName = "";
43
+ const nameToken = ctx.tokens[pos];
44
+ if (nameToken?.type === "TEXT" || nameToken?.type === "IDENTIFIER") {
45
+ moduleName = nameToken.value;
46
+ pos++;
47
+ consumed++;
48
+ }
49
+
50
+ // Parse remaining attributes
51
+ const attrResult = parseAttributesRaw(ctx, pos);
52
+ pos += attrResult.consumed;
53
+ consumed += attrResult.consumed;
54
+
55
+ // Expect ]]
56
+ if (ctx.tokens[pos]?.type !== "BLOCK_CLOSE") {
57
+ return { success: false };
58
+ }
59
+ pos++;
60
+ consumed++;
61
+
62
+ // Dispatch to specific module parser
63
+ const moduleParseRule = getModuleRuleByName(moduleName);
64
+
65
+ // Check for body based on module rule's hasBody flag
66
+ // For unknown modules, default to no body (they will be parsed as bodyless)
67
+ let body: string | undefined;
68
+ const moduleHasBody = moduleParseRule?.hasBody ?? false;
69
+
70
+ if (moduleHasBody && ctx.tokens[pos]?.type === "NEWLINE") {
71
+ pos++;
72
+ consumed++;
73
+
74
+ let bodyContent = "";
75
+ let foundClose = false;
76
+ while (pos < ctx.tokens.length) {
77
+ const token = ctx.tokens[pos];
78
+ if (!token || token.type === "EOF") {
79
+ break;
80
+ }
81
+
82
+ if (token.type === "BLOCK_END_OPEN") {
83
+ const closeNameResult = parseBlockName(ctx, pos + 1);
84
+ if (
85
+ closeNameResult &&
86
+ (closeNameResult.name === "module" || closeNameResult.name === "module654")
87
+ ) {
88
+ foundClose = true;
89
+ pos++;
90
+ consumed++;
91
+ pos += closeNameResult.consumed;
92
+ consumed += closeNameResult.consumed;
93
+ if (ctx.tokens[pos]?.type === "BLOCK_CLOSE") {
94
+ pos++;
95
+ consumed++;
96
+ }
97
+ if (ctx.tokens[pos]?.type === "NEWLINE") {
98
+ pos++;
99
+ consumed++;
100
+ }
101
+ break;
102
+ }
103
+ }
104
+
105
+ bodyContent += token.value;
106
+ pos++;
107
+ consumed++;
108
+ }
109
+
110
+ if (!foundClose) {
111
+ ctx.diagnostics.push({
112
+ severity: "warning",
113
+ code: "unclosed-block",
114
+ message: "Missing closing tag [[/module]] for [[module]]",
115
+ position: openToken.position,
116
+ });
117
+ }
118
+
119
+ if (bodyContent.trim()) {
120
+ body = bodyContent.trim();
121
+ }
122
+ }
123
+
124
+ if (moduleParseRule) {
125
+ const result = moduleParseRule.parse(ctx, pos, attrResult.attrs, body);
126
+
127
+ // Element を直接返すモジュール(CSS等)
128
+ if ("element" in result) {
129
+ return {
130
+ success: true,
131
+ elements: [result as Element],
132
+ consumed,
133
+ };
134
+ }
135
+
136
+ return {
137
+ success: true,
138
+ elements: [
139
+ {
140
+ element: "module",
141
+ data: result,
142
+ },
143
+ ],
144
+ consumed,
145
+ };
146
+ }
147
+
148
+ return {
149
+ success: true,
150
+ elements: [
151
+ {
152
+ element: "module",
153
+ data: {
154
+ module: "unknown",
155
+ name: moduleName,
156
+ arguments: attrResult.attrs,
157
+ body,
158
+ },
159
+ },
160
+ ],
161
+ consumed,
162
+ };
163
+ },
164
+ };