@wdprlib/parser 1.1.4 → 2.0.2

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @wdprlib/parser
2
2
 
3
- Wikidot markup parser.
3
+ Parser for Wikidot markup.
4
4
 
5
5
  ## Installation
6
6
 
@@ -56,6 +56,7 @@ const resolved = await resolveModules(ast, {
56
56
 
57
57
  - [@wdprlib/ast](https://www.npmjs.com/package/@wdprlib/ast) - AST type definitions
58
58
  - [@wdprlib/render](https://www.npmjs.com/package/@wdprlib/render) - HTML renderer
59
+ - [@wdprlib/decompiler](https://www.npmjs.com/package/@wdprlib/decompiler) - HTML to Wikidot decompiler
59
60
  - [@wdprlib/runtime](https://www.npmjs.com/package/@wdprlib/runtime) - Client-side runtime
60
61
 
61
62
  ## License
package/dist/index.cjs CHANGED
@@ -1528,6 +1528,17 @@ function parseLiItem(ctx, startPos, listType) {
1528
1528
  pos++;
1529
1529
  }
1530
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
+ }
1531
1542
  if (isLiClose(ctx, pos)) {
1532
1543
  const closeConsumed = consumeCloseTag(ctx, pos);
1533
1544
  consumed += closeConsumed;
@@ -1620,11 +1631,13 @@ function parseListBlock(ctx, startPos, listType) {
1620
1631
  consumed++;
1621
1632
  }
1622
1633
  const items = [];
1634
+ let foundListClose = false;
1623
1635
  while (pos < ctx.tokens.length) {
1624
1636
  const token = ctx.tokens[pos];
1625
1637
  if (!token || token.type === "EOF")
1626
1638
  break;
1627
1639
  if (isListClose(ctx, pos, listType)) {
1640
+ foundListClose = true;
1628
1641
  const closeConsumed = consumeCloseTag(ctx, pos);
1629
1642
  consumed += closeConsumed;
1630
1643
  break;
@@ -1748,6 +1761,17 @@ function parseListBlock(ctx, startPos, listType) {
1748
1761
  });
1749
1762
  }
1750
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
+ }
1751
1775
  const listData = {
1752
1776
  type: listType === "ol" ? "numbered" : "bullet",
1753
1777
  attributes: attrResult.attrs,
@@ -2183,8 +2207,19 @@ var divRule = {
2183
2207
  if (ctx.tokens[pos]?.type !== "NEWLINE") {
2184
2208
  return consumeFailedDiv(ctx);
2185
2209
  }
2210
+ if (ctx.divClosesBudget === 0) {
2211
+ return { success: false };
2212
+ }
2186
2213
  pos++;
2187
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
+ }
2188
2223
  const closeCondition = (checkCtx) => {
2189
2224
  const token = checkCtx.tokens[checkCtx.pos];
2190
2225
  if (token?.type === "BLOCK_END_OPEN") {
@@ -2195,7 +2230,7 @@ var divRule = {
2195
2230
  }
2196
2231
  return false;
2197
2232
  };
2198
- const bodyCtx = { ...ctx, pos };
2233
+ const bodyCtx = { ...ctx, pos, divClosesBudget: bodyBudget };
2199
2234
  let children;
2200
2235
  if (paragraphStrip) {
2201
2236
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
@@ -2208,6 +2243,14 @@ var divRule = {
2208
2243
  pos += bodyResult.consumed;
2209
2244
  children = bodyResult.elements;
2210
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
+ }
2211
2254
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
2212
2255
  pos++;
2213
2256
  consumed++;
@@ -2241,6 +2284,21 @@ var divRule = {
2241
2284
  };
2242
2285
  }
2243
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
+ }
2244
2302
  function consumeFailedDiv(ctx) {
2245
2303
  const elements = [];
2246
2304
  let pos = ctx.pos;
@@ -2252,6 +2310,20 @@ function consumeFailedDiv(ctx) {
2252
2310
  const t = ctx.tokens[scanPos];
2253
2311
  if (!t || t.type === "EOF")
2254
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
+ }
2255
2327
  if (t.type === "BLOCK_END_OPEN") {
2256
2328
  const nameResult = parseBlockName(ctx, scanPos + 1);
2257
2329
  if (nameResult?.name === "div") {
@@ -2268,6 +2340,23 @@ function consumeFailedDiv(ctx) {
2268
2340
  if (lastClosePos === -1) {
2269
2341
  return { success: false };
2270
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
+ }
2271
2360
  const endPos = lastClosePos + lastCloseConsumed;
2272
2361
  while (pos < endPos && pos < ctx.tokens.length) {
2273
2362
  const t = ctx.tokens[pos];
@@ -2384,6 +2473,7 @@ var codeBlockRule = {
2384
2473
  consumed++;
2385
2474
  }
2386
2475
  let codeContent = "";
2476
+ let foundClose = closingSwallowed;
2387
2477
  while (!closingSwallowed && pos < ctx.tokens.length) {
2388
2478
  const token = ctx.tokens[pos];
2389
2479
  if (!token || token.type === "EOF") {
@@ -2392,6 +2482,7 @@ var codeBlockRule = {
2392
2482
  if (token.type === "BLOCK_END_OPEN") {
2393
2483
  const closeNameResult = parseBlockName(ctx, pos + 1);
2394
2484
  if (closeNameResult && closeNameResult.name === "code") {
2485
+ foundClose = true;
2395
2486
  pos++;
2396
2487
  consumed++;
2397
2488
  pos += closeNameResult.consumed;
@@ -2411,6 +2502,14 @@ var codeBlockRule = {
2411
2502
  pos++;
2412
2503
  consumed++;
2413
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
+ }
2414
2513
  codeContent = codeContent.replace(/\n$/, "");
2415
2514
  const codeBlockData = {
2416
2515
  contents: codeContent,
@@ -2569,6 +2668,7 @@ var collapsibleRule = {
2569
2668
  }
2570
2669
  pos++;
2571
2670
  consumed++;
2671
+ const openPosition = openToken.position;
2572
2672
  const hasNewlineAfterOpen = ctx.tokens[pos]?.type === "NEWLINE";
2573
2673
  if (hasNewlineAfterOpen) {
2574
2674
  pos++;
@@ -2619,6 +2719,14 @@ var collapsibleRule = {
2619
2719
  pos += bodyResult.consumed;
2620
2720
  bodyElements = mergeParagraphs(bodyResult.elements);
2621
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
+ }
2622
2730
  if (isCollapsibleClose(ctx, pos)) {
2623
2731
  const closeConsumed = consumeCloseTag2(ctx, pos);
2624
2732
  consumed += closeConsumed;
@@ -2947,6 +3055,7 @@ var tableBlockRule = {
2947
3055
  consumed++;
2948
3056
  }
2949
3057
  const rows = [];
3058
+ let foundTableClose = false;
2950
3059
  while (pos < ctx.tokens.length) {
2951
3060
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
2952
3061
  pos++;
@@ -2959,6 +3068,7 @@ var tableBlockRule = {
2959
3068
  if (token.type === "BLOCK_END_OPEN") {
2960
3069
  const closeNameResult = parseBlockName(ctx, pos + 1);
2961
3070
  if (closeNameResult?.name === "table") {
3071
+ foundTableClose = true;
2962
3072
  pos++;
2963
3073
  consumed++;
2964
3074
  pos += closeNameResult.consumed;
@@ -2989,6 +3099,14 @@ var tableBlockRule = {
2989
3099
  pos++;
2990
3100
  consumed++;
2991
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
+ }
2992
3110
  const hasValidContent = rows.some((row) => row.cells.length > 0);
2993
3111
  if (!hasValidContent) {
2994
3112
  return { success: false };
@@ -3036,6 +3154,7 @@ function parseRow(ctx, startPos) {
3036
3154
  consumed++;
3037
3155
  }
3038
3156
  const cells = [];
3157
+ let foundRowClose = false;
3039
3158
  while (pos < ctx.tokens.length) {
3040
3159
  while (ctx.tokens[pos]?.type === "WHITESPACE" || ctx.tokens[pos]?.type === "NEWLINE") {
3041
3160
  pos++;
@@ -3048,6 +3167,7 @@ function parseRow(ctx, startPos) {
3048
3167
  if (token.type === "BLOCK_END_OPEN") {
3049
3168
  const closeNameResult = parseBlockName(ctx, pos + 1);
3050
3169
  if (closeNameResult?.name === "row") {
3170
+ foundRowClose = true;
3051
3171
  pos++;
3052
3172
  consumed++;
3053
3173
  pos += closeNameResult.consumed;
@@ -3078,6 +3198,17 @@ function parseRow(ctx, startPos) {
3078
3198
  pos++;
3079
3199
  consumed++;
3080
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
+ }
3081
3212
  return {
3082
3213
  row: {
3083
3214
  attributes: attrResult.attrs,
@@ -3140,6 +3271,17 @@ function parseCell(ctx, startPos) {
3140
3271
  consumed += bodyResult.consumed;
3141
3272
  pos += bodyResult.consumed;
3142
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
+ }
3143
3285
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
3144
3286
  pos++;
3145
3287
  consumed++;
@@ -3597,6 +3739,7 @@ var moduleRule = {
3597
3739
  pos++;
3598
3740
  consumed++;
3599
3741
  let bodyContent = "";
3742
+ let foundClose = false;
3600
3743
  while (pos < ctx.tokens.length) {
3601
3744
  const token = ctx.tokens[pos];
3602
3745
  if (!token || token.type === "EOF") {
@@ -3605,6 +3748,7 @@ var moduleRule = {
3605
3748
  if (token.type === "BLOCK_END_OPEN") {
3606
3749
  const closeNameResult = parseBlockName(ctx, pos + 1);
3607
3750
  if (closeNameResult && (closeNameResult.name === "module" || closeNameResult.name === "module654")) {
3751
+ foundClose = true;
3608
3752
  pos++;
3609
3753
  consumed++;
3610
3754
  pos += closeNameResult.consumed;
@@ -3624,6 +3768,14 @@ var moduleRule = {
3624
3768
  pos++;
3625
3769
  consumed++;
3626
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
+ }
3627
3779
  if (bodyContent.trim()) {
3628
3780
  body = bodyContent.trim();
3629
3781
  }
@@ -4017,7 +4169,16 @@ var alignRule = {
4017
4169
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4018
4170
  consumed += bodyResult.consumed;
4019
4171
  pos += bodyResult.consumed;
4172
+ const directionSymbol = { left: "<", right: ">", center: "=", justify: "==" }[direction];
4020
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
+ }
4021
4182
  if (closeCheck.match) {
4022
4183
  consumed += closeCheck.consumed;
4023
4184
  pos += closeCheck.consumed;
@@ -4105,6 +4266,17 @@ function parseTab(ctx) {
4105
4266
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4106
4267
  consumed += bodyResult.consumed;
4107
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
+ }
4108
4280
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4109
4281
  pos++;
4110
4282
  consumed++;
@@ -4168,6 +4340,9 @@ var tabviewRule = {
4168
4340
  const tabs = [];
4169
4341
  const tabCtx = { ...ctx, pos };
4170
4342
  while (pos < ctx.tokens.length) {
4343
+ if (ctx.tokens[pos]?.type === "EOF") {
4344
+ break;
4345
+ }
4171
4346
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4172
4347
  const closeNameResult = parseBlockName(ctx, pos + 1);
4173
4348
  const closeName = closeNameResult?.name.toLowerCase();
@@ -4189,6 +4364,19 @@ var tabviewRule = {
4189
4364
  }
4190
4365
  }
4191
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
+ }
4192
4380
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4193
4381
  pos++;
4194
4382
  consumed++;
@@ -4427,6 +4615,14 @@ var mathBlockRule = {
4427
4615
  pos++;
4428
4616
  consumed++;
4429
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
+ }
4430
4626
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4431
4627
  pos++;
4432
4628
  consumed++;
@@ -4509,6 +4705,12 @@ var htmlBlockRule = {
4509
4705
  consumed++;
4510
4706
  }
4511
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
+ });
4512
4714
  return { success: false };
4513
4715
  }
4514
4716
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4598,6 +4800,12 @@ var embedBlockRule = {
4598
4800
  consumed++;
4599
4801
  }
4600
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
+ });
4601
4809
  return { success: false };
4602
4810
  }
4603
4811
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
@@ -4827,6 +5035,14 @@ var iftagsRule = {
4827
5035
  const bodyResult = parseBlocksUntil(bodyCtx, closeCondition);
4828
5036
  consumed += bodyResult.consumed;
4829
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
+ }
4830
5046
  if (ctx.tokens[pos]?.type === "BLOCK_END_OPEN") {
4831
5047
  pos++;
4832
5048
  consumed++;
@@ -5030,6 +5246,12 @@ var orphanLiRule = {
5030
5246
  consumed++;
5031
5247
  }
5032
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
+ });
5033
5255
  return { success: false };
5034
5256
  }
5035
5257
  return {
@@ -5220,6 +5442,12 @@ var bibliographyRule = {
5220
5442
  consumed++;
5221
5443
  }
5222
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
+ });
5223
5451
  return { success: false };
5224
5452
  }
5225
5453
  const definitionItems = entries.map((entry) => ({
@@ -6141,6 +6369,7 @@ var commentRule = {
6141
6369
  name: "comment",
6142
6370
  startTokens: ["COMMENT_OPEN"],
6143
6371
  parse(ctx) {
6372
+ const openToken = currentToken(ctx);
6144
6373
  let pos = ctx.pos + 1;
6145
6374
  let consumed = 1;
6146
6375
  while (pos < ctx.tokens.length) {
@@ -6158,11 +6387,23 @@ var commentRule = {
6158
6387
  };
6159
6388
  }
6160
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
+ });
6161
6396
  return { success: false };
6162
6397
  }
6163
6398
  pos++;
6164
6399
  consumed++;
6165
6400
  }
6401
+ ctx.diagnostics.push({
6402
+ severity: "warning",
6403
+ code: "unclosed-comment",
6404
+ message: "Unterminated comment: missing closing --]",
6405
+ position: openToken.position
6406
+ });
6166
6407
  return { success: false };
6167
6408
  }
6168
6409
  };
@@ -6420,6 +6661,12 @@ var spanRule = {
6420
6661
  }
6421
6662
  }
6422
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
+ });
6423
6670
  return { success: false };
6424
6671
  }
6425
6672
  if (paragraphStrip) {
@@ -6620,6 +6867,7 @@ var sizeRule = {
6620
6867
  pos++;
6621
6868
  consumed++;
6622
6869
  const children = [];
6870
+ let foundClose = false;
6623
6871
  while (pos < ctx.tokens.length) {
6624
6872
  const token = ctx.tokens[pos];
6625
6873
  if (!token || token.type === "EOF") {
@@ -6628,6 +6876,7 @@ var sizeRule = {
6628
6876
  if (token.type === "BLOCK_END_OPEN") {
6629
6877
  const closeNameResult = parseBlockName(ctx, pos + 1);
6630
6878
  if (closeNameResult && closeNameResult.name === "size") {
6879
+ foundClose = true;
6631
6880
  pos++;
6632
6881
  consumed++;
6633
6882
  pos += closeNameResult.consumed;
@@ -6651,6 +6900,14 @@ var sizeRule = {
6651
6900
  consumed++;
6652
6901
  }
6653
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
+ }
6654
6911
  return {
6655
6912
  success: true,
6656
6913
  elements: [
@@ -6700,6 +6957,7 @@ var footnoteRule = {
6700
6957
  consumed++;
6701
6958
  const paragraphs = [[]];
6702
6959
  let currentParagraph = 0;
6960
+ let foundClose = false;
6703
6961
  while (pos < ctx.tokens.length) {
6704
6962
  const token = ctx.tokens[pos];
6705
6963
  if (!token || token.type === "EOF") {
@@ -6708,6 +6966,7 @@ var footnoteRule = {
6708
6966
  if (token.type === "BLOCK_END_OPEN") {
6709
6967
  const closeNameResult = parseBlockName(ctx, pos + 1);
6710
6968
  if (closeNameResult && closeNameResult.name === "footnote") {
6969
+ foundClose = true;
6711
6970
  pos++;
6712
6971
  consumed++;
6713
6972
  pos += closeNameResult.consumed;
@@ -6784,6 +7043,14 @@ var footnoteRule = {
6784
7043
  });
6785
7044
  }
6786
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
+ }
6787
7054
  ctx.footnotes.push(children);
6788
7055
  return {
6789
7056
  success: true,
@@ -7279,6 +7546,12 @@ var anchorRule = {
7279
7546
  }
7280
7547
  }
7281
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
+ });
7282
7555
  return { success: false };
7283
7556
  }
7284
7557
  if (paragraphStrip) {
@@ -8257,6 +8530,50 @@ function cleanElement(el) {
8257
8530
  }
8258
8531
  return el;
8259
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
+ }
8260
8577
  // packages/parser/src/parser/toc.ts
8261
8578
  class TocIndexer {
8262
8579
  index = 0;
@@ -8334,6 +8651,7 @@ class Parser {
8334
8651
  htmlBlocks: [],
8335
8652
  footnoteBlockParsed: false,
8336
8653
  bibcites: [],
8654
+ diagnostics: [],
8337
8655
  blockRules,
8338
8656
  blockFallbackRule: paragraphRule,
8339
8657
  inlineRules
@@ -8346,7 +8664,8 @@ class Parser {
8346
8664
  children.push(...blocks);
8347
8665
  }
8348
8666
  const mergedChildren = mergeSpanStripParagraphs(children);
8349
- const cleanedChildren = cleanInternalFlags(mergedChildren);
8667
+ const divProcessed = suppressDivAdjacentParagraphs(mergedChildren);
8668
+ const cleanedChildren = cleanInternalFlags(divProcessed);
8350
8669
  const hasFootnoteBlock = cleanedChildren.some((el) => el.element === "footnote-block");
8351
8670
  if (!hasFootnoteBlock) {
8352
8671
  cleanedChildren.push({
@@ -8370,7 +8689,7 @@ class Parser {
8370
8689
  if (this.ctx.htmlBlocks.length > 0) {
8371
8690
  result["html-blocks"] = this.ctx.htmlBlocks;
8372
8691
  }
8373
- return result;
8692
+ return { ast: result, diagnostics: this.ctx.diagnostics };
8374
8693
  }
8375
8694
  isAtEnd() {
8376
8695
  return this.ctx.pos >= this.ctx.tokens.length || this.currentToken().type === "EOF";