@wdprlib/parser 1.1.2 → 2.0.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.
package/dist/index.cjs CHANGED
@@ -31,7 +31,7 @@ var __export = (target, all) => {
31
31
  var exports_src = {};
32
32
  __export(exports_src, {
33
33
  tokenize: () => tokenize,
34
- text: () => import_ast3.text,
34
+ text: () => import_ast5.text,
35
35
  resolveModules: () => resolveModules,
36
36
  resolveListUsers: () => resolveListUsers,
37
37
  resolveIncludes: () => resolveIncludes,
@@ -42,34 +42,35 @@ __export(exports_src, {
42
42
  parseDateSelector: () => parseDateSelector,
43
43
  parseCategory: () => parseCategory,
44
44
  parse: () => parse,
45
- paragraph: () => import_ast3.paragraph,
45
+ paragraph: () => import_ast5.paragraph,
46
46
  normalizeQuery: () => normalizeQuery,
47
- listItemSubList: () => import_ast3.listItemSubList,
48
- listItemElements: () => import_ast3.listItemElements,
49
- list: () => import_ast3.list,
50
- link: () => import_ast3.link,
51
- lineBreak: () => import_ast3.lineBreak,
52
- italics: () => import_ast3.italics,
47
+ listItemSubList: () => import_ast5.listItemSubList,
48
+ listItemElements: () => import_ast5.listItemElements,
49
+ list: () => import_ast5.list,
50
+ link: () => import_ast5.link,
51
+ lineBreak: () => import_ast5.lineBreak,
52
+ italics: () => import_ast5.italics,
53
53
  isListUsersModule: () => isListUsersModule2,
54
- horizontalRule: () => import_ast3.horizontalRule,
55
- heading: () => import_ast3.heading,
54
+ horizontalRule: () => import_ast5.horizontalRule,
55
+ heading: () => import_ast5.heading,
56
56
  extractListUsersVariables: () => extractListUsersVariables,
57
57
  extractDataRequirements: () => extractDataRequirements,
58
58
  createToken: () => createToken,
59
- createSettings: () => import_ast4.createSettings,
60
- createPosition: () => import_ast3.createPosition,
61
- createPoint: () => import_ast3.createPoint,
62
- container: () => import_ast3.container,
59
+ createSettings: () => import_ast6.createSettings,
60
+ createPosition: () => import_ast5.createPosition,
61
+ createPoint: () => import_ast5.createPoint,
62
+ container: () => import_ast5.container,
63
63
  compileTemplate: () => compileTemplate,
64
64
  compileListUsersTemplate: () => compileListUsersTemplate,
65
- bold: () => import_ast3.bold,
65
+ bold: () => import_ast5.bold,
66
+ STYLE_SLOT_PREFIX: () => import_ast4.STYLE_SLOT_PREFIX,
66
67
  Parser: () => Parser,
67
68
  Lexer: () => Lexer,
68
- DEFAULT_SETTINGS: () => import_ast4.DEFAULT_SETTINGS
69
+ DEFAULT_SETTINGS: () => import_ast6.DEFAULT_SETTINGS
69
70
  });
70
71
  module.exports = __toCommonJS(exports_src);
71
- var import_ast3 = require("@wdprlib/ast");
72
- var import_ast4 = require("@wdprlib/ast");
72
+ var import_ast5 = require("@wdprlib/ast");
73
+ var import_ast6 = require("@wdprlib/ast");
73
74
 
74
75
  // packages/parser/src/lexer/tokens.ts
75
76
  function createToken(type, value, position, lineStart = false) {
@@ -1527,6 +1528,17 @@ function parseLiItem(ctx, startPos, listType) {
1527
1528
  pos++;
1528
1529
  }
1529
1530
  }
1531
+ if (!isLiClose(ctx, pos)) {
1532
+ ctx.diagnostics.push({
1533
+ severity: "warning",
1534
+ code: "unclosed-block",
1535
+ message: "Missing closing tag [[/li]] for [[li]]",
1536
+ position: ctx.tokens[startPos]?.position ?? {
1537
+ start: { line: 0, column: 0, offset: 0 },
1538
+ end: { line: 0, column: 0, offset: 0 }
1539
+ }
1540
+ });
1541
+ }
1530
1542
  if (isLiClose(ctx, pos)) {
1531
1543
  const closeConsumed = consumeCloseTag(ctx, pos);
1532
1544
  consumed += closeConsumed;
@@ -1619,11 +1631,13 @@ function parseListBlock(ctx, startPos, listType) {
1619
1631
  consumed++;
1620
1632
  }
1621
1633
  const items = [];
1634
+ let foundListClose = false;
1622
1635
  while (pos < ctx.tokens.length) {
1623
1636
  const token = ctx.tokens[pos];
1624
1637
  if (!token || token.type === "EOF")
1625
1638
  break;
1626
1639
  if (isListClose(ctx, pos, listType)) {
1640
+ foundListClose = true;
1627
1641
  const closeConsumed = consumeCloseTag(ctx, pos);
1628
1642
  consumed += closeConsumed;
1629
1643
  break;
@@ -1747,6 +1761,17 @@ function parseListBlock(ctx, startPos, listType) {
1747
1761
  });
1748
1762
  }
1749
1763
  }
1764
+ if (!foundListClose) {
1765
+ ctx.diagnostics.push({
1766
+ severity: "warning",
1767
+ code: "unclosed-block",
1768
+ message: `Missing closing tag [[/${listType}]] for [[${listType}]]`,
1769
+ position: ctx.tokens[startPos]?.position ?? {
1770
+ start: { line: 0, column: 0, offset: 0 },
1771
+ end: { line: 0, column: 0, offset: 0 }
1772
+ }
1773
+ });
1774
+ }
1750
1775
  const listData = {
1751
1776
  type: listType === "ol" ? "numbered" : "bullet",
1752
1777
  attributes: attrResult.attrs,
@@ -2182,8 +2207,19 @@ var divRule = {
2182
2207
  if (ctx.tokens[pos]?.type !== "NEWLINE") {
2183
2208
  return consumeFailedDiv(ctx);
2184
2209
  }
2210
+ if (ctx.divClosesBudget === 0) {
2211
+ return { success: false };
2212
+ }
2185
2213
  pos++;
2186
2214
  consumed++;
2215
+ const openPosition = openToken.position;
2216
+ let bodyBudget;
2217
+ if (ctx.divClosesBudget !== undefined) {
2218
+ bodyBudget = ctx.divClosesBudget - 1;
2219
+ } else {
2220
+ const closesInScope = countDivCloses(ctx, pos);
2221
+ bodyBudget = closesInScope > 0 ? closesInScope - 1 : 0;
2222
+ }
2187
2223
  const closeCondition = (checkCtx) => {
2188
2224
  const token = checkCtx.tokens[checkCtx.pos];
2189
2225
  if (token?.type === "BLOCK_END_OPEN") {
@@ -2194,7 +2230,7 @@ var divRule = {
2194
2230
  }
2195
2231
  return false;
2196
2232
  };
2197
- const bodyCtx = { ...ctx, pos };
2233
+ const bodyCtx = { ...ctx, pos, divClosesBudget: bodyBudget };
2198
2234
  let children;
2199
2235
  if (paragraphStrip) {
2200
2236
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
@@ -2207,6 +2243,14 @@ var divRule = {
2207
2243
  pos += bodyResult.consumed;
2208
2244
  children = bodyResult.elements;
2209
2245
  }
2246
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
2247
+ ctx.diagnostics.push({
2248
+ severity: "warning",
2249
+ code: "unclosed-block",
2250
+ message: `Missing closing tag [[/div]] for [[${blockName}]]`,
2251
+ position: openPosition
2252
+ });
2253
+ }
2210
2254
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
2211
2255
  pos++;
2212
2256
  consumed++;
@@ -2240,6 +2284,21 @@ var divRule = {
2240
2284
  };
2241
2285
  }
2242
2286
  };
2287
+ function countDivCloses(ctx, startPos) {
2288
+ let count = 0;
2289
+ for (let i = startPos;i < ctx.tokens.length; i++) {
2290
+ const t = ctx.tokens[i];
2291
+ if (!t || t.type === "EOF")
2292
+ break;
2293
+ if (t.type === "BLOCK_END_OPEN") {
2294
+ const nameResult = parseBlockName(ctx, i + 1);
2295
+ if (nameResult?.name === "div") {
2296
+ count++;
2297
+ }
2298
+ }
2299
+ }
2300
+ return count;
2301
+ }
2243
2302
  function consumeFailedDiv(ctx) {
2244
2303
  const elements = [];
2245
2304
  let pos = ctx.pos;
@@ -2251,6 +2310,20 @@ function consumeFailedDiv(ctx) {
2251
2310
  const t = ctx.tokens[scanPos];
2252
2311
  if (!t || t.type === "EOF")
2253
2312
  break;
2313
+ if (t.type === "BLOCK_OPEN" && t.lineStart && scanPos > pos) {
2314
+ const nameResult = parseBlockName(ctx, scanPos + 1);
2315
+ if (nameResult?.name === "div" || nameResult?.name === "div_") {
2316
+ let checkPos = scanPos + 1 + nameResult.consumed;
2317
+ const attrResult = parseAttributes(ctx, checkPos);
2318
+ checkPos += attrResult.consumed;
2319
+ if (ctx.tokens[checkPos]?.type === "BLOCK_CLOSE") {
2320
+ checkPos++;
2321
+ if (ctx.tokens[checkPos]?.type === "NEWLINE" || ctx.tokens[checkPos]?.type === "EOF") {
2322
+ break;
2323
+ }
2324
+ }
2325
+ }
2326
+ }
2254
2327
  if (t.type === "BLOCK_END_OPEN") {
2255
2328
  const nameResult = parseBlockName(ctx, scanPos + 1);
2256
2329
  if (nameResult?.name === "div") {
@@ -2267,6 +2340,23 @@ function consumeFailedDiv(ctx) {
2267
2340
  if (lastClosePos === -1) {
2268
2341
  return { success: false };
2269
2342
  }
2343
+ const endPosForDiag = lastClosePos;
2344
+ for (let diagPos = ctx.pos;diagPos < endPosForDiag; diagPos++) {
2345
+ const t = ctx.tokens[diagPos];
2346
+ if (t?.type === "BLOCK_OPEN") {
2347
+ const nameResult = parseBlockName(ctx, diagPos + 1);
2348
+ if (nameResult?.name === "div" || nameResult?.name === "div_") {
2349
+ if (t.position) {
2350
+ ctx.diagnostics.push({
2351
+ severity: "error",
2352
+ code: "inline-block-element",
2353
+ message: `[[${nameResult.name}]] must be followed by a newline to be a block element`,
2354
+ position: t.position
2355
+ });
2356
+ }
2357
+ }
2358
+ }
2359
+ }
2270
2360
  const endPos = lastClosePos + lastCloseConsumed;
2271
2361
  while (pos < endPos && pos < ctx.tokens.length) {
2272
2362
  const t = ctx.tokens[pos];
@@ -2383,6 +2473,7 @@ var codeBlockRule = {
2383
2473
  consumed++;
2384
2474
  }
2385
2475
  let codeContent = "";
2476
+ let foundClose = closingSwallowed;
2386
2477
  while (!closingSwallowed && pos < ctx.tokens.length) {
2387
2478
  const token = ctx.tokens[pos];
2388
2479
  if (!token || token.type === "EOF") {
@@ -2391,6 +2482,7 @@ var codeBlockRule = {
2391
2482
  if (token.type === "BLOCK_END_OPEN") {
2392
2483
  const closeNameResult = parseBlockName(ctx, pos + 1);
2393
2484
  if (closeNameResult && closeNameResult.name === "code") {
2485
+ foundClose = true;
2394
2486
  pos++;
2395
2487
  consumed++;
2396
2488
  pos += closeNameResult.consumed;
@@ -2410,6 +2502,14 @@ var codeBlockRule = {
2410
2502
  pos++;
2411
2503
  consumed++;
2412
2504
  }
2505
+ if (!foundClose) {
2506
+ ctx.diagnostics.push({
2507
+ severity: "warning",
2508
+ code: "unclosed-block",
2509
+ message: "Missing closing tag [[/code]] for [[code]]",
2510
+ position: openToken.position
2511
+ });
2512
+ }
2413
2513
  codeContent = codeContent.replace(/\n$/, "");
2414
2514
  const codeBlockData = {
2415
2515
  contents: codeContent,
@@ -2568,6 +2668,7 @@ var collapsibleRule = {
2568
2668
  }
2569
2669
  pos++;
2570
2670
  consumed++;
2671
+ const openPosition = openToken.position;
2571
2672
  const hasNewlineAfterOpen = ctx.tokens[pos]?.type === "NEWLINE";
2572
2673
  if (hasNewlineAfterOpen) {
2573
2674
  pos++;
@@ -2618,6 +2719,14 @@ var collapsibleRule = {
2618
2719
  pos += bodyResult.consumed;
2619
2720
  bodyElements = mergeParagraphs(bodyResult.elements);
2620
2721
  }
2722
+ if (!isCollapsibleClose(ctx, pos)) {
2723
+ ctx.diagnostics.push({
2724
+ severity: "warning",
2725
+ code: "unclosed-block",
2726
+ message: "Missing closing tag [[/collapsible]] for [[collapsible]]",
2727
+ position: openPosition
2728
+ });
2729
+ }
2621
2730
  if (isCollapsibleClose(ctx, pos)) {
2622
2731
  const closeConsumed = consumeCloseTag2(ctx, pos);
2623
2732
  consumed += closeConsumed;
@@ -2946,6 +3055,7 @@ var tableBlockRule = {
2946
3055
  consumed++;
2947
3056
  }
2948
3057
  const rows = [];
3058
+ let foundTableClose = false;
2949
3059
  while (pos < ctx.tokens.length) {
2950
3060
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
2951
3061
  pos++;
@@ -2958,6 +3068,7 @@ var tableBlockRule = {
2958
3068
  if (token.type === "BLOCK_END_OPEN") {
2959
3069
  const closeNameResult = parseBlockName(ctx, pos + 1);
2960
3070
  if (closeNameResult?.name === "table") {
3071
+ foundTableClose = true;
2961
3072
  pos++;
2962
3073
  consumed++;
2963
3074
  pos += closeNameResult.consumed;
@@ -2988,6 +3099,14 @@ var tableBlockRule = {
2988
3099
  pos++;
2989
3100
  consumed++;
2990
3101
  }
3102
+ if (!foundTableClose) {
3103
+ ctx.diagnostics.push({
3104
+ severity: "warning",
3105
+ code: "unclosed-block",
3106
+ message: "Missing closing tag [[/table]] for [[table]]",
3107
+ position: openToken.position
3108
+ });
3109
+ }
2991
3110
  const hasValidContent = rows.some((row) => row.cells.length > 0);
2992
3111
  if (!hasValidContent) {
2993
3112
  return { success: false };
@@ -3035,6 +3154,7 @@ function parseRow(ctx, startPos) {
3035
3154
  consumed++;
3036
3155
  }
3037
3156
  const cells = [];
3157
+ let foundRowClose = false;
3038
3158
  while (pos < ctx.tokens.length) {
3039
3159
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
3040
3160
  pos++;
@@ -3047,6 +3167,7 @@ function parseRow(ctx, startPos) {
3047
3167
  if (token.type === "BLOCK_END_OPEN") {
3048
3168
  const closeNameResult = parseBlockName(ctx, pos + 1);
3049
3169
  if (closeNameResult?.name === "row") {
3170
+ foundRowClose = true;
3050
3171
  pos++;
3051
3172
  consumed++;
3052
3173
  pos += closeNameResult.consumed;
@@ -3077,6 +3198,17 @@ function parseRow(ctx, startPos) {
3077
3198
  pos++;
3078
3199
  consumed++;
3079
3200
  }
3201
+ if (!foundRowClose) {
3202
+ ctx.diagnostics.push({
3203
+ severity: "warning",
3204
+ code: "unclosed-block",
3205
+ message: "Missing closing tag [[/row]] for [[row]]",
3206
+ position: ctx.tokens[startPos]?.position ?? {
3207
+ start: { line: 0, column: 0, offset: 0 },
3208
+ end: { line: 0, column: 0, offset: 0 }
3209
+ }
3210
+ });
3211
+ }
3080
3212
  return {
3081
3213
  row: {
3082
3214
  attributes: attrResult.attrs,
@@ -3139,6 +3271,17 @@ function parseCell(ctx, startPos) {
3139
3271
  consumed += bodyResult.consumed;
3140
3272
  pos += bodyResult.consumed;
3141
3273
  const hadParagraphBreaks = bodyResult.hadParagraphBreaks;
3274
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
3275
+ ctx.diagnostics.push({
3276
+ severity: "warning",
3277
+ code: "unclosed-block",
3278
+ message: `Missing closing tag [[/${closeName}]] for [[${closeName}]]`,
3279
+ position: ctx.tokens[startPos]?.position ?? {
3280
+ start: { line: 0, column: 0, offset: 0 },
3281
+ end: { line: 0, column: 0, offset: 0 }
3282
+ }
3283
+ });
3284
+ }
3142
3285
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
3143
3286
  pos++;
3144
3287
  consumed++;
@@ -3596,6 +3739,7 @@ var moduleRule = {
3596
3739
  pos++;
3597
3740
  consumed++;
3598
3741
  let bodyContent = "";
3742
+ let foundClose = false;
3599
3743
  while (pos < ctx.tokens.length) {
3600
3744
  const token = ctx.tokens[pos];
3601
3745
  if (!token || token.type === "EOF") {
@@ -3604,6 +3748,7 @@ var moduleRule = {
3604
3748
  if (token.type === "BLOCK_END_OPEN") {
3605
3749
  const closeNameResult = parseBlockName(ctx, pos + 1);
3606
3750
  if (closeNameResult && (closeNameResult.name === "module" || closeNameResult.name === "module654")) {
3751
+ foundClose = true;
3607
3752
  pos++;
3608
3753
  consumed++;
3609
3754
  pos += closeNameResult.consumed;
@@ -3623,6 +3768,14 @@ var moduleRule = {
3623
3768
  pos++;
3624
3769
  consumed++;
3625
3770
  }
3771
+ if (!foundClose) {
3772
+ ctx.diagnostics.push({
3773
+ severity: "warning",
3774
+ code: "unclosed-block",
3775
+ message: "Missing closing tag [[/module]] for [[module]]",
3776
+ position: openToken.position
3777
+ });
3778
+ }
3626
3779
  if (bodyContent.trim()) {
3627
3780
  body = bodyContent.trim();
3628
3781
  }
@@ -4016,7 +4169,16 @@ var alignRule = {
4016
4169
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4017
4170
  consumed += bodyResult.consumed;
4018
4171
  pos += bodyResult.consumed;
4172
+ const directionSymbol = { left: "<", right: ">", center: "=", justify: "==" }[direction];
4019
4173
  const closeCheck = isAlignClose({ ...ctx, pos }, direction);
4174
+ if (!closeCheck.match) {
4175
+ ctx.diagnostics.push({
4176
+ severity: "warning",
4177
+ code: "unclosed-block",
4178
+ message: `Missing closing tag [[/${directionSymbol}]] for [[${directionSymbol}]]`,
4179
+ position: openToken.position
4180
+ });
4181
+ }
4020
4182
  if (closeCheck.match) {
4021
4183
  consumed += closeCheck.consumed;
4022
4184
  pos += closeCheck.consumed;
@@ -4104,6 +4266,17 @@ function parseTab(ctx) {
4104
4266
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4105
4267
  consumed += bodyResult.consumed;
4106
4268
  pos += bodyResult.consumed;
4269
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4270
+ ctx.diagnostics.push({
4271
+ severity: "warning",
4272
+ code: "unclosed-block",
4273
+ message: "Missing closing tag [[/tab]] for [[tab]]",
4274
+ position: ctx.tokens[ctx.pos]?.position ?? {
4275
+ start: { line: 0, column: 0, offset: 0 },
4276
+ end: { line: 0, column: 0, offset: 0 }
4277
+ }
4278
+ });
4279
+ }
4107
4280
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4108
4281
  pos++;
4109
4282
  consumed++;
@@ -4167,6 +4340,9 @@ var tabviewRule = {
4167
4340
  const tabs = [];
4168
4341
  const tabCtx = { ...ctx, pos };
4169
4342
  while (pos < ctx.tokens.length) {
4343
+ if (ctx.tokens[pos]?.type === "EOF") {
4344
+ break;
4345
+ }
4170
4346
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4171
4347
  const closeNameResult = parseBlockName(ctx, pos + 1);
4172
4348
  const closeName = closeNameResult?.name.toLowerCase();
@@ -4188,6 +4364,19 @@ var tabviewRule = {
4188
4364
  }
4189
4365
  }
4190
4366
  }
4367
+ const hasTabviewClose = ctx.tokens[pos]?.type === "BLOCK_END_OPEN" && (() => {
4368
+ const n = parseBlockName(ctx, pos + 1);
4369
+ const name = n?.name.toLowerCase();
4370
+ return name === "tabview" || name === "tabs";
4371
+ })();
4372
+ if (!hasTabviewClose) {
4373
+ ctx.diagnostics.push({
4374
+ severity: "warning",
4375
+ code: "unclosed-block",
4376
+ message: `Missing closing tag [[/${blockName}]] for [[${blockName}]]`,
4377
+ position: openToken.position
4378
+ });
4379
+ }
4191
4380
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4192
4381
  pos++;
4193
4382
  consumed++;
@@ -4426,6 +4615,14 @@ var mathBlockRule = {
4426
4615
  pos++;
4427
4616
  consumed++;
4428
4617
  }
4618
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4619
+ ctx.diagnostics.push({
4620
+ severity: "warning",
4621
+ code: "unclosed-block",
4622
+ message: "Missing closing tag [[/math]] for [[math]]",
4623
+ position: openToken.position
4624
+ });
4625
+ }
4429
4626
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4430
4627
  pos++;
4431
4628
  consumed++;
@@ -4508,6 +4705,12 @@ var htmlBlockRule = {
4508
4705
  consumed++;
4509
4706
  }
4510
4707
  if (!foundClose) {
4708
+ ctx.diagnostics.push({
4709
+ severity: "warning",
4710
+ code: "unclosed-block",
4711
+ message: "Missing closing tag [[/html]] for [[html]]",
4712
+ position: openToken.position
4713
+ });
4511
4714
  return { success: false };
4512
4715
  }
4513
4716
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4597,6 +4800,12 @@ var embedBlockRule = {
4597
4800
  consumed++;
4598
4801
  }
4599
4802
  if (!foundClose) {
4803
+ ctx.diagnostics.push({
4804
+ severity: "warning",
4805
+ code: "unclosed-block",
4806
+ message: `Missing closing tag [[/${blockName}]] for [[${blockName}]]`,
4807
+ position: openToken.position
4808
+ });
4600
4809
  return { success: false };
4601
4810
  }
4602
4811
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4826,6 +5035,14 @@ var iftagsRule = {
4826
5035
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4827
5036
  consumed += bodyResult.consumed;
4828
5037
  pos += bodyResult.consumed;
5038
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
5039
+ ctx.diagnostics.push({
5040
+ severity: "warning",
5041
+ code: "unclosed-block",
5042
+ message: "Missing closing tag [[/iftags]] for [[iftags]]",
5043
+ position: openToken.position
5044
+ });
5045
+ }
4829
5046
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4830
5047
  pos++;
4831
5048
  consumed++;
@@ -5029,6 +5246,12 @@ var orphanLiRule = {
5029
5246
  consumed++;
5030
5247
  }
5031
5248
  if (!foundClose) {
5249
+ ctx.diagnostics.push({
5250
+ severity: "warning",
5251
+ code: "unclosed-block",
5252
+ message: "Missing closing tag [[/li]] for [[li]]",
5253
+ position: openToken.position
5254
+ });
5032
5255
  return { success: false };
5033
5256
  }
5034
5257
  return {
@@ -5219,6 +5442,12 @@ var bibliographyRule = {
5219
5442
  consumed++;
5220
5443
  }
5221
5444
  if (!foundClose) {
5445
+ ctx.diagnostics.push({
5446
+ severity: "warning",
5447
+ code: "unclosed-block",
5448
+ message: "Missing closing tag [[/bibliography]] for [[bibliography]]",
5449
+ position: openToken.position
5450
+ });
5222
5451
  return { success: false };
5223
5452
  }
5224
5453
  const definitionItems = entries.map((entry) => ({
@@ -6140,6 +6369,7 @@ var commentRule = {
6140
6369
  name: "comment",
6141
6370
  startTokens: ["COMMENT_OPEN"],
6142
6371
  parse(ctx) {
6372
+ const openToken = currentToken(ctx);
6143
6373
  let pos = ctx.pos + 1;
6144
6374
  let consumed = 1;
6145
6375
  while (pos < ctx.tokens.length) {
@@ -6157,11 +6387,23 @@ var commentRule = {
6157
6387
  };
6158
6388
  }
6159
6389
  if (token.type === "EOF") {
6390
+ ctx.diagnostics.push({
6391
+ severity: "warning",
6392
+ code: "unclosed-comment",
6393
+ message: "Unterminated comment: missing closing --]",
6394
+ position: openToken.position
6395
+ });
6160
6396
  return { success: false };
6161
6397
  }
6162
6398
  pos++;
6163
6399
  consumed++;
6164
6400
  }
6401
+ ctx.diagnostics.push({
6402
+ severity: "warning",
6403
+ code: "unclosed-comment",
6404
+ message: "Unterminated comment: missing closing --]",
6405
+ position: openToken.position
6406
+ });
6165
6407
  return { success: false };
6166
6408
  }
6167
6409
  };
@@ -6419,6 +6661,12 @@ var spanRule = {
6419
6661
  }
6420
6662
  }
6421
6663
  if (!foundClose) {
6664
+ ctx.diagnostics.push({
6665
+ severity: "warning",
6666
+ code: "unclosed-block",
6667
+ message: `Missing closing tag [[/span]] for [[${blockName}]]`,
6668
+ position: openToken.position
6669
+ });
6422
6670
  return { success: false };
6423
6671
  }
6424
6672
  if (paragraphStrip) {
@@ -6619,6 +6867,7 @@ var sizeRule = {
6619
6867
  pos++;
6620
6868
  consumed++;
6621
6869
  const children = [];
6870
+ let foundClose = false;
6622
6871
  while (pos < ctx.tokens.length) {
6623
6872
  const token = ctx.tokens[pos];
6624
6873
  if (!token || token.type === "EOF") {
@@ -6627,6 +6876,7 @@ var sizeRule = {
6627
6876
  if (token.type === "BLOCK_END_OPEN") {
6628
6877
  const closeNameResult = parseBlockName(ctx, pos + 1);
6629
6878
  if (closeNameResult && closeNameResult.name === "size") {
6879
+ foundClose = true;
6630
6880
  pos++;
6631
6881
  consumed++;
6632
6882
  pos += closeNameResult.consumed;
@@ -6650,6 +6900,14 @@ var sizeRule = {
6650
6900
  consumed++;
6651
6901
  }
6652
6902
  }
6903
+ if (!foundClose) {
6904
+ ctx.diagnostics.push({
6905
+ severity: "warning",
6906
+ code: "unclosed-block",
6907
+ message: "Missing closing tag [[/size]] for [[size]]",
6908
+ position: openToken.position
6909
+ });
6910
+ }
6653
6911
  return {
6654
6912
  success: true,
6655
6913
  elements: [
@@ -6699,6 +6957,7 @@ var footnoteRule = {
6699
6957
  consumed++;
6700
6958
  const paragraphs = [[]];
6701
6959
  let currentParagraph = 0;
6960
+ let foundClose = false;
6702
6961
  while (pos < ctx.tokens.length) {
6703
6962
  const token = ctx.tokens[pos];
6704
6963
  if (!token || token.type === "EOF") {
@@ -6707,6 +6966,7 @@ var footnoteRule = {
6707
6966
  if (token.type === "BLOCK_END_OPEN") {
6708
6967
  const closeNameResult = parseBlockName(ctx, pos + 1);
6709
6968
  if (closeNameResult && closeNameResult.name === "footnote") {
6969
+ foundClose = true;
6710
6970
  pos++;
6711
6971
  consumed++;
6712
6972
  pos += closeNameResult.consumed;
@@ -6783,6 +7043,14 @@ var footnoteRule = {
6783
7043
  });
6784
7044
  }
6785
7045
  }
7046
+ if (!foundClose) {
7047
+ ctx.diagnostics.push({
7048
+ severity: "warning",
7049
+ code: "unclosed-block",
7050
+ message: "Missing closing tag [[/footnote]] for [[footnote]]",
7051
+ position: openToken.position
7052
+ });
7053
+ }
6786
7054
  ctx.footnotes.push(children);
6787
7055
  return {
6788
7056
  success: true,
@@ -7278,6 +7546,12 @@ var anchorRule = {
7278
7546
  }
7279
7547
  }
7280
7548
  if (!foundClose) {
7549
+ ctx.diagnostics.push({
7550
+ severity: "warning",
7551
+ code: "unclosed-block",
7552
+ message: `Missing closing tag [[/a]] for [[${nameResult.name}]]`,
7553
+ position: openToken.position
7554
+ });
7281
7555
  return { success: false };
7282
7556
  }
7283
7557
  if (paragraphStrip) {
@@ -8256,6 +8530,50 @@ function cleanElement(el) {
8256
8530
  }
8257
8531
  return el;
8258
8532
  }
8533
+ // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
8534
+ function isParagraphContainer2(el) {
8535
+ if (!el || el.element !== "container")
8536
+ return false;
8537
+ return el.data.type === "paragraph";
8538
+ }
8539
+ function isDivContainer(el) {
8540
+ if (!el || el.element !== "container")
8541
+ return false;
8542
+ return el.data.type === "div";
8543
+ }
8544
+ function suppressAtLevel(elements) {
8545
+ if (elements.length <= 1)
8546
+ return elements;
8547
+ const unwrap = new Array(elements.length).fill(false);
8548
+ for (let i = 0;i < elements.length; i++) {
8549
+ if (!isParagraphContainer2(elements[i]))
8550
+ continue;
8551
+ const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
8552
+ const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
8553
+ if (prevIsDiv || nextIsDiv) {
8554
+ unwrap[i] = true;
8555
+ }
8556
+ }
8557
+ const result = [];
8558
+ for (let i = 0;i < elements.length; i++) {
8559
+ const el = elements[i];
8560
+ if (!el)
8561
+ continue;
8562
+ if (unwrap[i] && el.element === "container") {
8563
+ const inner = el.data.elements;
8564
+ if (i > 0 && isDivContainer(elements[i - 1])) {
8565
+ result.push({ element: "line-break" });
8566
+ }
8567
+ result.push(...inner);
8568
+ } else {
8569
+ result.push(el);
8570
+ }
8571
+ }
8572
+ return result;
8573
+ }
8574
+ function suppressDivAdjacentParagraphs(elements) {
8575
+ return suppressAtLevel(elements);
8576
+ }
8259
8577
  // packages/parser/src/parser/toc.ts
8260
8578
  class TocIndexer {
8261
8579
  index = 0;
@@ -8333,6 +8651,7 @@ class Parser {
8333
8651
  htmlBlocks: [],
8334
8652
  footnoteBlockParsed: false,
8335
8653
  bibcites: [],
8654
+ diagnostics: [],
8336
8655
  blockRules,
8337
8656
  blockFallbackRule: paragraphRule,
8338
8657
  inlineRules
@@ -8345,7 +8664,8 @@ class Parser {
8345
8664
  children.push(...blocks);
8346
8665
  }
8347
8666
  const mergedChildren = mergeSpanStripParagraphs(children);
8348
- const cleanedChildren = cleanInternalFlags(mergedChildren);
8667
+ const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
8668
+ const cleanedChildren = cleanInternalFlags(divProcessed);
8349
8669
  const hasFootnoteBlock = cleanedChildren.some((el) => el.element === "footnote-block");
8350
8670
  if (!hasFootnoteBlock) {
8351
8671
  cleanedChildren.push({
@@ -8369,7 +8689,7 @@ class Parser {
8369
8689
  if (this.ctx.htmlBlocks.length > 0) {
8370
8690
  result["html-blocks"] = this.ctx.htmlBlocks;
8371
8691
  }
8372
- return result;
8692
+ return { ast: result, diagnostics: this.ctx.diagnostics };
8373
8693
  }
8374
8694
  isAtEnd() {
8375
8695
  return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
@@ -9580,6 +9900,7 @@ function resolveAndNormalizeQuery(requirement, urlParams) {
9580
9900
  function parseTagCondition(condition) {
9581
9901
  const required = [];
9582
9902
  const forbidden = [];
9903
+ const optional = [];
9583
9904
  const parts = condition.trim().split(/\s+/);
9584
9905
  for (const part of parts) {
9585
9906
  if (!part)
@@ -9593,12 +9914,15 @@ function parseTagCondition(condition) {
9593
9914
  if (tag)
9594
9915
  forbidden.push(tag);
9595
9916
  } else {
9596
- required.push(part);
9917
+ optional.push(part);
9597
9918
  }
9598
9919
  }
9599
- return { required, forbidden };
9920
+ return { required, forbidden, optional };
9600
9921
  }
9601
9922
  function evaluateTagCondition(condition, pageTags) {
9923
+ if (condition.required.length === 0 && condition.forbidden.length === 0 && condition.optional.length === 0) {
9924
+ return false;
9925
+ }
9602
9926
  const tagSet = new Set(pageTags);
9603
9927
  for (const tag of condition.required) {
9604
9928
  if (!tagSet.has(tag)) {
@@ -9610,6 +9934,11 @@ function evaluateTagCondition(condition, pageTags) {
9610
9934
  return false;
9611
9935
  }
9612
9936
  }
9937
+ if (condition.optional.length > 0) {
9938
+ if (!condition.optional.some((tag) => tagSet.has(tag))) {
9939
+ return false;
9940
+ }
9941
+ }
9613
9942
  return true;
9614
9943
  }
9615
9944
  // packages/parser/src/parser/rules/block/module/iftags/resolve.ts
@@ -9745,6 +10074,7 @@ function resolveListUsers(_module, data, compiledTemplate, parse2) {
9745
10074
  return itemAst.elements;
9746
10075
  }
9747
10076
  // packages/parser/src/parser/rules/block/module/resolve.ts
10077
+ var import_ast3 = require("@wdprlib/ast");
9748
10078
  async function resolveModules(ast, dataProvider, options) {
9749
10079
  let listPagesCtx = null;
9750
10080
  const listPagesReqs = options.requirements.listPages ?? [];
@@ -9909,18 +10239,31 @@ function countModulesInElements(elements) {
9909
10239
  }
9910
10240
  function collectStyles(elements) {
9911
10241
  const styles = [];
9912
- const filtered = collectStylesFromElements(elements, styles);
10242
+ const ctx = { nextSlotId: 0 };
10243
+ const filtered = collectStylesFromElements(elements, styles, ctx);
9913
10244
  return { elements: filtered, styles };
9914
10245
  }
9915
- function collectStylesFromElements(elements, styles) {
10246
+ function collectStylesFromElements(elements, styles, ctx) {
9916
10247
  const result = [];
9917
10248
  for (const element of elements) {
9918
10249
  if (element.element === "style") {
9919
10250
  styles.push(element.data);
9920
10251
  continue;
9921
10252
  }
9922
- const mapped = mapElementChildren(element, (children) => collectStylesFromElements(children, styles));
10253
+ if (element.element === "if-tags") {
10254
+ const slotId = ctx.nextSlotId++;
10255
+ styles.push(`${import_ast3.STYLE_SLOT_PREFIX}${slotId}`);
10256
+ result.push({
10257
+ element: "if-tags",
10258
+ data: { ...element.data, _styleSlot: slotId }
10259
+ });
10260
+ continue;
10261
+ }
10262
+ const mapped = mapElementChildren(element, (children) => collectStylesFromElements(children, styles, ctx));
9923
10263
  result.push(mapped);
9924
10264
  }
9925
10265
  return result;
9926
10266
  }
10267
+
10268
+ // packages/parser/src/parser/rules/block/module/index.ts
10269
+ var import_ast4 = require("@wdprlib/ast");