@wdprlib/parser 1.1.4 → 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.js CHANGED
@@ -1473,6 +1473,17 @@ function parseLiItem(ctx, startPos, listType) {
1473
1473
  pos++;
1474
1474
  }
1475
1475
  }
1476
+ if (!isLiClose(ctx, pos)) {
1477
+ ctx.diagnostics.push({
1478
+ severity: "warning",
1479
+ code: "unclosed-block",
1480
+ message: "Missing closing tag [[/li]] for [[li]]",
1481
+ position: ctx.tokens[startPos]?.position ?? {
1482
+ start: { line: 0, column: 0, offset: 0 },
1483
+ end: { line: 0, column: 0, offset: 0 }
1484
+ }
1485
+ });
1486
+ }
1476
1487
  if (isLiClose(ctx, pos)) {
1477
1488
  const closeConsumed = consumeCloseTag(ctx, pos);
1478
1489
  consumed += closeConsumed;
@@ -1565,11 +1576,13 @@ function parseListBlock(ctx, startPos, listType) {
1565
1576
  consumed++;
1566
1577
  }
1567
1578
  const items = [];
1579
+ let foundListClose = false;
1568
1580
  while (pos < ctx.tokens.length) {
1569
1581
  const token = ctx.tokens[pos];
1570
1582
  if (!token || token.type === "EOF")
1571
1583
  break;
1572
1584
  if (isListClose(ctx, pos, listType)) {
1585
+ foundListClose = true;
1573
1586
  const closeConsumed = consumeCloseTag(ctx, pos);
1574
1587
  consumed += closeConsumed;
1575
1588
  break;
@@ -1693,6 +1706,17 @@ function parseListBlock(ctx, startPos, listType) {
1693
1706
  });
1694
1707
  }
1695
1708
  }
1709
+ if (!foundListClose) {
1710
+ ctx.diagnostics.push({
1711
+ severity: "warning",
1712
+ code: "unclosed-block",
1713
+ message: `Missing closing tag [[/${listType}]] for [[${listType}]]`,
1714
+ position: ctx.tokens[startPos]?.position ?? {
1715
+ start: { line: 0, column: 0, offset: 0 },
1716
+ end: { line: 0, column: 0, offset: 0 }
1717
+ }
1718
+ });
1719
+ }
1696
1720
  const listData = {
1697
1721
  type: listType === "ol" ? "numbered" : "bullet",
1698
1722
  attributes: attrResult.attrs,
@@ -2128,8 +2152,19 @@ var divRule = {
2128
2152
  if (ctx.tokens[pos]?.type !== "NEWLINE") {
2129
2153
  return consumeFailedDiv(ctx);
2130
2154
  }
2155
+ if (ctx.divClosesBudget === 0) {
2156
+ return { success: false };
2157
+ }
2131
2158
  pos++;
2132
2159
  consumed++;
2160
+ const openPosition = openToken.position;
2161
+ let bodyBudget;
2162
+ if (ctx.divClosesBudget !== undefined) {
2163
+ bodyBudget = ctx.divClosesBudget - 1;
2164
+ } else {
2165
+ const closesInScope = countDivCloses(ctx, pos);
2166
+ bodyBudget = closesInScope > 0 ? closesInScope - 1 : 0;
2167
+ }
2133
2168
  const closeCondition = (checkCtx) => {
2134
2169
  const token = checkCtx.tokens[checkCtx.pos];
2135
2170
  if (token?.type === "BLOCK_END_OPEN") {
@@ -2140,7 +2175,7 @@ var divRule = {
2140
2175
  }
2141
2176
  return false;
2142
2177
  };
2143
- const bodyCtx = { ...ctx, pos };
2178
+ const bodyCtx = { ...ctx, pos, divClosesBudget: bodyBudget };
2144
2179
  let children;
2145
2180
  if (paragraphStrip) {
2146
2181
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
@@ -2153,6 +2188,14 @@ var divRule = {
2153
2188
  pos += bodyResult.consumed;
2154
2189
  children = bodyResult.elements;
2155
2190
  }
2191
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
2192
+ ctx.diagnostics.push({
2193
+ severity: "warning",
2194
+ code: "unclosed-block",
2195
+ message: `Missing closing tag [[/div]] for [[${blockName}]]`,
2196
+ position: openPosition
2197
+ });
2198
+ }
2156
2199
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
2157
2200
  pos++;
2158
2201
  consumed++;
@@ -2186,6 +2229,21 @@ var divRule = {
2186
2229
  };
2187
2230
  }
2188
2231
  };
2232
+ function countDivCloses(ctx, startPos) {
2233
+ let count = 0;
2234
+ for (let i = startPos;i < ctx.tokens.length; i++) {
2235
+ const t = ctx.tokens[i];
2236
+ if (!t || t.type === "EOF")
2237
+ break;
2238
+ if (t.type === "BLOCK_END_OPEN") {
2239
+ const nameResult = parseBlockName(ctx, i + 1);
2240
+ if (nameResult?.name === "div") {
2241
+ count++;
2242
+ }
2243
+ }
2244
+ }
2245
+ return count;
2246
+ }
2189
2247
  function consumeFailedDiv(ctx) {
2190
2248
  const elements = [];
2191
2249
  let pos = ctx.pos;
@@ -2197,6 +2255,20 @@ function consumeFailedDiv(ctx) {
2197
2255
  const t = ctx.tokens[scanPos];
2198
2256
  if (!t || t.type === "EOF")
2199
2257
  break;
2258
+ if (t.type === "BLOCK_OPEN" && t.lineStart && scanPos > pos) {
2259
+ const nameResult = parseBlockName(ctx, scanPos + 1);
2260
+ if (nameResult?.name === "div" || nameResult?.name === "div_") {
2261
+ let checkPos = scanPos + 1 + nameResult.consumed;
2262
+ const attrResult = parseAttributes(ctx, checkPos);
2263
+ checkPos += attrResult.consumed;
2264
+ if (ctx.tokens[checkPos]?.type === "BLOCK_CLOSE") {
2265
+ checkPos++;
2266
+ if (ctx.tokens[checkPos]?.type === "NEWLINE" || ctx.tokens[checkPos]?.type === "EOF") {
2267
+ break;
2268
+ }
2269
+ }
2270
+ }
2271
+ }
2200
2272
  if (t.type === "BLOCK_END_OPEN") {
2201
2273
  const nameResult = parseBlockName(ctx, scanPos + 1);
2202
2274
  if (nameResult?.name === "div") {
@@ -2213,6 +2285,23 @@ function consumeFailedDiv(ctx) {
2213
2285
  if (lastClosePos === -1) {
2214
2286
  return { success: false };
2215
2287
  }
2288
+ const endPosForDiag = lastClosePos;
2289
+ for (let diagPos = ctx.pos;diagPos < endPosForDiag; diagPos++) {
2290
+ const t = ctx.tokens[diagPos];
2291
+ if (t?.type === "BLOCK_OPEN") {
2292
+ const nameResult = parseBlockName(ctx, diagPos + 1);
2293
+ if (nameResult?.name === "div" || nameResult?.name === "div_") {
2294
+ if (t.position) {
2295
+ ctx.diagnostics.push({
2296
+ severity: "error",
2297
+ code: "inline-block-element",
2298
+ message: `[[${nameResult.name}]] must be followed by a newline to be a block element`,
2299
+ position: t.position
2300
+ });
2301
+ }
2302
+ }
2303
+ }
2304
+ }
2216
2305
  const endPos = lastClosePos + lastCloseConsumed;
2217
2306
  while (pos < endPos && pos < ctx.tokens.length) {
2218
2307
  const t = ctx.tokens[pos];
@@ -2329,6 +2418,7 @@ var codeBlockRule = {
2329
2418
  consumed++;
2330
2419
  }
2331
2420
  let codeContent = "";
2421
+ let foundClose = closingSwallowed;
2332
2422
  while (!closingSwallowed && pos < ctx.tokens.length) {
2333
2423
  const token = ctx.tokens[pos];
2334
2424
  if (!token || token.type === "EOF") {
@@ -2337,6 +2427,7 @@ var codeBlockRule = {
2337
2427
  if (token.type === "BLOCK_END_OPEN") {
2338
2428
  const closeNameResult = parseBlockName(ctx, pos + 1);
2339
2429
  if (closeNameResult && closeNameResult.name === "code") {
2430
+ foundClose = true;
2340
2431
  pos++;
2341
2432
  consumed++;
2342
2433
  pos += closeNameResult.consumed;
@@ -2356,6 +2447,14 @@ var codeBlockRule = {
2356
2447
  pos++;
2357
2448
  consumed++;
2358
2449
  }
2450
+ if (!foundClose) {
2451
+ ctx.diagnostics.push({
2452
+ severity: "warning",
2453
+ code: "unclosed-block",
2454
+ message: "Missing closing tag [[/code]] for [[code]]",
2455
+ position: openToken.position
2456
+ });
2457
+ }
2359
2458
  codeContent = codeContent.replace(/\n$/, "");
2360
2459
  const codeBlockData = {
2361
2460
  contents: codeContent,
@@ -2514,6 +2613,7 @@ var collapsibleRule = {
2514
2613
  }
2515
2614
  pos++;
2516
2615
  consumed++;
2616
+ const openPosition = openToken.position;
2517
2617
  const hasNewlineAfterOpen = ctx.tokens[pos]?.type === "NEWLINE";
2518
2618
  if (hasNewlineAfterOpen) {
2519
2619
  pos++;
@@ -2564,6 +2664,14 @@ var collapsibleRule = {
2564
2664
  pos += bodyResult.consumed;
2565
2665
  bodyElements = mergeParagraphs(bodyResult.elements);
2566
2666
  }
2667
+ if (!isCollapsibleClose(ctx, pos)) {
2668
+ ctx.diagnostics.push({
2669
+ severity: "warning",
2670
+ code: "unclosed-block",
2671
+ message: "Missing closing tag [[/collapsible]] for [[collapsible]]",
2672
+ position: openPosition
2673
+ });
2674
+ }
2567
2675
  if (isCollapsibleClose(ctx, pos)) {
2568
2676
  const closeConsumed = consumeCloseTag2(ctx, pos);
2569
2677
  consumed += closeConsumed;
@@ -2892,6 +3000,7 @@ var tableBlockRule = {
2892
3000
  consumed++;
2893
3001
  }
2894
3002
  const rows = [];
3003
+ let foundTableClose = false;
2895
3004
  while (pos < ctx.tokens.length) {
2896
3005
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
2897
3006
  pos++;
@@ -2904,6 +3013,7 @@ var tableBlockRule = {
2904
3013
  if (token.type === "BLOCK_END_OPEN") {
2905
3014
  const closeNameResult = parseBlockName(ctx, pos + 1);
2906
3015
  if (closeNameResult?.name === "table") {
3016
+ foundTableClose = true;
2907
3017
  pos++;
2908
3018
  consumed++;
2909
3019
  pos += closeNameResult.consumed;
@@ -2934,6 +3044,14 @@ var tableBlockRule = {
2934
3044
  pos++;
2935
3045
  consumed++;
2936
3046
  }
3047
+ if (!foundTableClose) {
3048
+ ctx.diagnostics.push({
3049
+ severity: "warning",
3050
+ code: "unclosed-block",
3051
+ message: "Missing closing tag [[/table]] for [[table]]",
3052
+ position: openToken.position
3053
+ });
3054
+ }
2937
3055
  const hasValidContent = rows.some((row) => row.cells.length > 0);
2938
3056
  if (!hasValidContent) {
2939
3057
  return { success: false };
@@ -2981,6 +3099,7 @@ function parseRow(ctx, startPos) {
2981
3099
  consumed++;
2982
3100
  }
2983
3101
  const cells = [];
3102
+ let foundRowClose = false;
2984
3103
  while (pos < ctx.tokens.length) {
2985
3104
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
2986
3105
  pos++;
@@ -2993,6 +3112,7 @@ function parseRow(ctx, startPos) {
2993
3112
  if (token.type === "BLOCK_END_OPEN") {
2994
3113
  const closeNameResult = parseBlockName(ctx, pos + 1);
2995
3114
  if (closeNameResult?.name === "row") {
3115
+ foundRowClose = true;
2996
3116
  pos++;
2997
3117
  consumed++;
2998
3118
  pos += closeNameResult.consumed;
@@ -3023,6 +3143,17 @@ function parseRow(ctx, startPos) {
3023
3143
  pos++;
3024
3144
  consumed++;
3025
3145
  }
3146
+ if (!foundRowClose) {
3147
+ ctx.diagnostics.push({
3148
+ severity: "warning",
3149
+ code: "unclosed-block",
3150
+ message: "Missing closing tag [[/row]] for [[row]]",
3151
+ position: ctx.tokens[startPos]?.position ?? {
3152
+ start: { line: 0, column: 0, offset: 0 },
3153
+ end: { line: 0, column: 0, offset: 0 }
3154
+ }
3155
+ });
3156
+ }
3026
3157
  return {
3027
3158
  row: {
3028
3159
  attributes: attrResult.attrs,
@@ -3085,6 +3216,17 @@ function parseCell(ctx, startPos) {
3085
3216
  consumed += bodyResult.consumed;
3086
3217
  pos += bodyResult.consumed;
3087
3218
  const hadParagraphBreaks = bodyResult.hadParagraphBreaks;
3219
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
3220
+ ctx.diagnostics.push({
3221
+ severity: "warning",
3222
+ code: "unclosed-block",
3223
+ message: `Missing closing tag [[/${closeName}]] for [[${closeName}]]`,
3224
+ position: ctx.tokens[startPos]?.position ?? {
3225
+ start: { line: 0, column: 0, offset: 0 },
3226
+ end: { line: 0, column: 0, offset: 0 }
3227
+ }
3228
+ });
3229
+ }
3088
3230
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
3089
3231
  pos++;
3090
3232
  consumed++;
@@ -3542,6 +3684,7 @@ var moduleRule = {
3542
3684
  pos++;
3543
3685
  consumed++;
3544
3686
  let bodyContent = "";
3687
+ let foundClose = false;
3545
3688
  while (pos < ctx.tokens.length) {
3546
3689
  const token = ctx.tokens[pos];
3547
3690
  if (!token || token.type === "EOF") {
@@ -3550,6 +3693,7 @@ var moduleRule = {
3550
3693
  if (token.type === "BLOCK_END_OPEN") {
3551
3694
  const closeNameResult = parseBlockName(ctx, pos + 1);
3552
3695
  if (closeNameResult && (closeNameResult.name === "module" || closeNameResult.name === "module654")) {
3696
+ foundClose = true;
3553
3697
  pos++;
3554
3698
  consumed++;
3555
3699
  pos += closeNameResult.consumed;
@@ -3569,6 +3713,14 @@ var moduleRule = {
3569
3713
  pos++;
3570
3714
  consumed++;
3571
3715
  }
3716
+ if (!foundClose) {
3717
+ ctx.diagnostics.push({
3718
+ severity: "warning",
3719
+ code: "unclosed-block",
3720
+ message: "Missing closing tag [[/module]] for [[module]]",
3721
+ position: openToken.position
3722
+ });
3723
+ }
3572
3724
  if (bodyContent.trim()) {
3573
3725
  body = bodyContent.trim();
3574
3726
  }
@@ -3962,7 +4114,16 @@ var alignRule = {
3962
4114
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
3963
4115
  consumed += bodyResult.consumed;
3964
4116
  pos += bodyResult.consumed;
4117
+ const directionSymbol = { left: "<", right: ">", center: "=", justify: "==" }[direction];
3965
4118
  const closeCheck = isAlignClose({ ...ctx, pos }, direction);
4119
+ if (!closeCheck.match) {
4120
+ ctx.diagnostics.push({
4121
+ severity: "warning",
4122
+ code: "unclosed-block",
4123
+ message: `Missing closing tag [[/${directionSymbol}]] for [[${directionSymbol}]]`,
4124
+ position: openToken.position
4125
+ });
4126
+ }
3966
4127
  if (closeCheck.match) {
3967
4128
  consumed += closeCheck.consumed;
3968
4129
  pos += closeCheck.consumed;
@@ -4050,6 +4211,17 @@ function parseTab(ctx) {
4050
4211
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4051
4212
  consumed += bodyResult.consumed;
4052
4213
  pos += bodyResult.consumed;
4214
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4215
+ ctx.diagnostics.push({
4216
+ severity: "warning",
4217
+ code: "unclosed-block",
4218
+ message: "Missing closing tag [[/tab]] for [[tab]]",
4219
+ position: ctx.tokens[ctx.pos]?.position ?? {
4220
+ start: { line: 0, column: 0, offset: 0 },
4221
+ end: { line: 0, column: 0, offset: 0 }
4222
+ }
4223
+ });
4224
+ }
4053
4225
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4054
4226
  pos++;
4055
4227
  consumed++;
@@ -4113,6 +4285,9 @@ var tabviewRule = {
4113
4285
  const tabs = [];
4114
4286
  const tabCtx = { ...ctx, pos };
4115
4287
  while (pos < ctx.tokens.length) {
4288
+ if (ctx.tokens[pos]?.type === "EOF") {
4289
+ break;
4290
+ }
4116
4291
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4117
4292
  const closeNameResult = parseBlockName(ctx, pos + 1);
4118
4293
  const closeName = closeNameResult?.name.toLowerCase();
@@ -4134,6 +4309,19 @@ var tabviewRule = {
4134
4309
  }
4135
4310
  }
4136
4311
  }
4312
+ const hasTabviewClose = ctx.tokens[pos]?.type === "BLOCK_END_OPEN" && (() => {
4313
+ const n = parseBlockName(ctx, pos + 1);
4314
+ const name = n?.name.toLowerCase();
4315
+ return name === "tabview" || name === "tabs";
4316
+ })();
4317
+ if (!hasTabviewClose) {
4318
+ ctx.diagnostics.push({
4319
+ severity: "warning",
4320
+ code: "unclosed-block",
4321
+ message: `Missing closing tag [[/${blockName}]] for [[${blockName}]]`,
4322
+ position: openToken.position
4323
+ });
4324
+ }
4137
4325
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4138
4326
  pos++;
4139
4327
  consumed++;
@@ -4372,6 +4560,14 @@ var mathBlockRule = {
4372
4560
  pos++;
4373
4561
  consumed++;
4374
4562
  }
4563
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4564
+ ctx.diagnostics.push({
4565
+ severity: "warning",
4566
+ code: "unclosed-block",
4567
+ message: "Missing closing tag [[/math]] for [[math]]",
4568
+ position: openToken.position
4569
+ });
4570
+ }
4375
4571
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4376
4572
  pos++;
4377
4573
  consumed++;
@@ -4454,6 +4650,12 @@ var htmlBlockRule = {
4454
4650
  consumed++;
4455
4651
  }
4456
4652
  if (!foundClose) {
4653
+ ctx.diagnostics.push({
4654
+ severity: "warning",
4655
+ code: "unclosed-block",
4656
+ message: "Missing closing tag [[/html]] for [[html]]",
4657
+ position: openToken.position
4658
+ });
4457
4659
  return { success: false };
4458
4660
  }
4459
4661
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4543,6 +4745,12 @@ var embedBlockRule = {
4543
4745
  consumed++;
4544
4746
  }
4545
4747
  if (!foundClose) {
4748
+ ctx.diagnostics.push({
4749
+ severity: "warning",
4750
+ code: "unclosed-block",
4751
+ message: `Missing closing tag [[/${blockName}]] for [[${blockName}]]`,
4752
+ position: openToken.position
4753
+ });
4546
4754
  return { success: false };
4547
4755
  }
4548
4756
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4772,6 +4980,14 @@ var iftagsRule = {
4772
4980
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4773
4981
  consumed += bodyResult.consumed;
4774
4982
  pos += bodyResult.consumed;
4983
+ if (ctx.tokens[pos]?.type !== "BLOCK_END_OPEN") {
4984
+ ctx.diagnostics.push({
4985
+ severity: "warning",
4986
+ code: "unclosed-block",
4987
+ message: "Missing closing tag [[/iftags]] for [[iftags]]",
4988
+ position: openToken.position
4989
+ });
4990
+ }
4775
4991
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4776
4992
  pos++;
4777
4993
  consumed++;
@@ -4975,6 +5191,12 @@ var orphanLiRule = {
4975
5191
  consumed++;
4976
5192
  }
4977
5193
  if (!foundClose) {
5194
+ ctx.diagnostics.push({
5195
+ severity: "warning",
5196
+ code: "unclosed-block",
5197
+ message: "Missing closing tag [[/li]] for [[li]]",
5198
+ position: openToken.position
5199
+ });
4978
5200
  return { success: false };
4979
5201
  }
4980
5202
  return {
@@ -5165,6 +5387,12 @@ var bibliographyRule = {
5165
5387
  consumed++;
5166
5388
  }
5167
5389
  if (!foundClose) {
5390
+ ctx.diagnostics.push({
5391
+ severity: "warning",
5392
+ code: "unclosed-block",
5393
+ message: "Missing closing tag [[/bibliography]] for [[bibliography]]",
5394
+ position: openToken.position
5395
+ });
5168
5396
  return { success: false };
5169
5397
  }
5170
5398
  const definitionItems = entries.map((entry) => ({
@@ -6086,6 +6314,7 @@ var commentRule = {
6086
6314
  name: "comment",
6087
6315
  startTokens: ["COMMENT_OPEN"],
6088
6316
  parse(ctx) {
6317
+ const openToken = currentToken(ctx);
6089
6318
  let pos = ctx.pos + 1;
6090
6319
  let consumed = 1;
6091
6320
  while (pos < ctx.tokens.length) {
@@ -6103,11 +6332,23 @@ var commentRule = {
6103
6332
  };
6104
6333
  }
6105
6334
  if (token.type === "EOF") {
6335
+ ctx.diagnostics.push({
6336
+ severity: "warning",
6337
+ code: "unclosed-comment",
6338
+ message: "Unterminated comment: missing closing --]",
6339
+ position: openToken.position
6340
+ });
6106
6341
  return { success: false };
6107
6342
  }
6108
6343
  pos++;
6109
6344
  consumed++;
6110
6345
  }
6346
+ ctx.diagnostics.push({
6347
+ severity: "warning",
6348
+ code: "unclosed-comment",
6349
+ message: "Unterminated comment: missing closing --]",
6350
+ position: openToken.position
6351
+ });
6111
6352
  return { success: false };
6112
6353
  }
6113
6354
  };
@@ -6365,6 +6606,12 @@ var spanRule = {
6365
6606
  }
6366
6607
  }
6367
6608
  if (!foundClose) {
6609
+ ctx.diagnostics.push({
6610
+ severity: "warning",
6611
+ code: "unclosed-block",
6612
+ message: `Missing closing tag [[/span]] for [[${blockName}]]`,
6613
+ position: openToken.position
6614
+ });
6368
6615
  return { success: false };
6369
6616
  }
6370
6617
  if (paragraphStrip) {
@@ -6565,6 +6812,7 @@ var sizeRule = {
6565
6812
  pos++;
6566
6813
  consumed++;
6567
6814
  const children = [];
6815
+ let foundClose = false;
6568
6816
  while (pos < ctx.tokens.length) {
6569
6817
  const token = ctx.tokens[pos];
6570
6818
  if (!token || token.type === "EOF") {
@@ -6573,6 +6821,7 @@ var sizeRule = {
6573
6821
  if (token.type === "BLOCK_END_OPEN") {
6574
6822
  const closeNameResult = parseBlockName(ctx, pos + 1);
6575
6823
  if (closeNameResult && closeNameResult.name === "size") {
6824
+ foundClose = true;
6576
6825
  pos++;
6577
6826
  consumed++;
6578
6827
  pos += closeNameResult.consumed;
@@ -6596,6 +6845,14 @@ var sizeRule = {
6596
6845
  consumed++;
6597
6846
  }
6598
6847
  }
6848
+ if (!foundClose) {
6849
+ ctx.diagnostics.push({
6850
+ severity: "warning",
6851
+ code: "unclosed-block",
6852
+ message: "Missing closing tag [[/size]] for [[size]]",
6853
+ position: openToken.position
6854
+ });
6855
+ }
6599
6856
  return {
6600
6857
  success: true,
6601
6858
  elements: [
@@ -6645,6 +6902,7 @@ var footnoteRule = {
6645
6902
  consumed++;
6646
6903
  const paragraphs = [[]];
6647
6904
  let currentParagraph = 0;
6905
+ let foundClose = false;
6648
6906
  while (pos < ctx.tokens.length) {
6649
6907
  const token = ctx.tokens[pos];
6650
6908
  if (!token || token.type === "EOF") {
@@ -6653,6 +6911,7 @@ var footnoteRule = {
6653
6911
  if (token.type === "BLOCK_END_OPEN") {
6654
6912
  const closeNameResult = parseBlockName(ctx, pos + 1);
6655
6913
  if (closeNameResult && closeNameResult.name === "footnote") {
6914
+ foundClose = true;
6656
6915
  pos++;
6657
6916
  consumed++;
6658
6917
  pos += closeNameResult.consumed;
@@ -6729,6 +6988,14 @@ var footnoteRule = {
6729
6988
  });
6730
6989
  }
6731
6990
  }
6991
+ if (!foundClose) {
6992
+ ctx.diagnostics.push({
6993
+ severity: "warning",
6994
+ code: "unclosed-block",
6995
+ message: "Missing closing tag [[/footnote]] for [[footnote]]",
6996
+ position: openToken.position
6997
+ });
6998
+ }
6732
6999
  ctx.footnotes.push(children);
6733
7000
  return {
6734
7001
  success: true,
@@ -7224,6 +7491,12 @@ var anchorRule = {
7224
7491
  }
7225
7492
  }
7226
7493
  if (!foundClose) {
7494
+ ctx.diagnostics.push({
7495
+ severity: "warning",
7496
+ code: "unclosed-block",
7497
+ message: `Missing closing tag [[/a]] for [[${nameResult.name}]]`,
7498
+ position: openToken.position
7499
+ });
7227
7500
  return { success: false };
7228
7501
  }
7229
7502
  if (paragraphStrip) {
@@ -8202,6 +8475,50 @@ function cleanElement(el) {
8202
8475
  }
8203
8476
  return el;
8204
8477
  }
8478
+ // packages/parser/src/parser/postprocess/divAdjacentParagraph.ts
8479
+ function isParagraphContainer2(el) {
8480
+ if (!el || el.element !== "container")
8481
+ return false;
8482
+ return el.data.type === "paragraph";
8483
+ }
8484
+ function isDivContainer(el) {
8485
+ if (!el || el.element !== "container")
8486
+ return false;
8487
+ return el.data.type === "div";
8488
+ }
8489
+ function suppressAtLevel(elements) {
8490
+ if (elements.length <= 1)
8491
+ return elements;
8492
+ const unwrap = new Array(elements.length).fill(false);
8493
+ for (let i = 0;i < elements.length; i++) {
8494
+ if (!isParagraphContainer2(elements[i]))
8495
+ continue;
8496
+ const prevIsDiv = i > 0 && isDivContainer(elements[i - 1]);
8497
+ const nextIsDiv = i < elements.length - 1 && isDivContainer(elements[i + 1]);
8498
+ if (prevIsDiv || nextIsDiv) {
8499
+ unwrap[i] = true;
8500
+ }
8501
+ }
8502
+ const result = [];
8503
+ for (let i = 0;i < elements.length; i++) {
8504
+ const el = elements[i];
8505
+ if (!el)
8506
+ continue;
8507
+ if (unwrap[i] && el.element === "container") {
8508
+ const inner = el.data.elements;
8509
+ if (i > 0 && isDivContainer(elements[i - 1])) {
8510
+ result.push({ element: "line-break" });
8511
+ }
8512
+ result.push(...inner);
8513
+ } else {
8514
+ result.push(el);
8515
+ }
8516
+ }
8517
+ return result;
8518
+ }
8519
+ function suppressDivAdjacentParagraphs(elements) {
8520
+ return suppressAtLevel(elements);
8521
+ }
8205
8522
  // packages/parser/src/parser/toc.ts
8206
8523
  class TocIndexer {
8207
8524
  index = 0;
@@ -8279,6 +8596,7 @@ class Parser {
8279
8596
  htmlBlocks: [],
8280
8597
  footnoteBlockParsed: false,
8281
8598
  bibcites: [],
8599
+ diagnostics: [],
8282
8600
  blockRules,
8283
8601
  blockFallbackRule: paragraphRule,
8284
8602
  inlineRules
@@ -8291,7 +8609,8 @@ class Parser {
8291
8609
  children.push(...blocks);
8292
8610
  }
8293
8611
  const mergedChildren = mergeSpanStripParagraphs(children);
8294
- const cleanedChildren = cleanInternalFlags(mergedChildren);
8612
+ const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
8613
+ const cleanedChildren = cleanInternalFlags(divProcessed);
8295
8614
  const hasFootnoteBlock = cleanedChildren.some((el) => el.element === "footnote-block");
8296
8615
  if (!hasFootnoteBlock) {
8297
8616
  cleanedChildren.push({
@@ -8315,7 +8634,7 @@ class Parser {
8315
8634
  if (this.ctx.htmlBlocks.length > 0) {
8316
8635
  result["html-blocks"] = this.ctx.htmlBlocks;
8317
8636
  }
8318
- return result;
8637
+ return { ast: result, diagnostics: this.ctx.diagnostics };
8319
8638
  }
8320
8639
  isAtEnd() {
8321
8640
  return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdprlib/parser",
3
- "version": "1.1.4",
3
+ "version": "2.0.0",
4
4
  "description": "Parser for Wikidot markup",
5
5
  "keywords": [
6
6
  "ast",
@@ -39,6 +39,6 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@braintree/sanitize-url": "^7.1.1",
42
- "@wdprlib/ast": "1.1.1"
42
+ "@wdprlib/ast": "1.2.0"
43
43
  }
44
44
  }