asciidoc-abundant-tree 0.2.0 → 0.2.1

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
@@ -274,6 +274,29 @@ const ttl = projection.ttl;
274
274
  const jsonLd = projection.jsonLd;
275
275
  ```
276
276
 
277
+ Book-entry callers can register include resolvers for includes that select by something other than a file path, such as a symbol in a TypeScript file. A resolver claims targets by prefix and returns whole consecutive lines of a source file inside `documentRoot`, plus the line they start at; the lines get coordinates in that file just like a file include:
278
+
279
+ ```ts
280
+ const book = await parseAbundantTree({
281
+ sourcePath: "bookshelf/books/08-design/book.adoc",
282
+ mode: "book-entry",
283
+ documentRoot: ".",
284
+ includeResolvers: [
285
+ {
286
+ // include::docloom:examples/replay.ts[symbol=replay,part=source]
287
+ prefix: "docloom:",
288
+ resolve: ({ target, attributes }) => {
289
+ // Your own selector: find the declaration, widen it to whole lines.
290
+ const { text, startLine } = selectWholeLines(target, attributes);
291
+ return { text, origin: { path: target, startLine } };
292
+ },
293
+ },
294
+ ],
295
+ });
296
+ ```
297
+
298
+ `attributes` are the include attributes as Asciidoctor parses them, the same an Asciidoctor include processor receives, so the bookshelf build and this parser can register the same selector. A resolver that throws fails construction with `include.resolver-failed`; returned text that does not match its source leaves those lines without coordinates and reports `origin-map.selection-mismatch`.
299
+
277
300
  `parseAbundantTree` reads only the supplied source file. Interdocument xrefs keep their raw target and official href when Asciidoctor exposes one, but this package does not open the referenced `.adoc` file.
278
301
 
279
302
  Browser tools that already hold AsciiDoc source text can use the browser-safe subpath:
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { o as exportAnimationYaml } from "../src-CNTTQlPJ.mjs";
2
+ import { o as exportAnimationYaml } from "../src-DMOLXZbF.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/animation-yaml-export/cli.ts
5
5
  const USAGE = `Usage:
package/dist/browser.mjs CHANGED
@@ -944,8 +944,8 @@ function recoveryFailure(code, message, source) {
944
944
  }
945
945
  //#endregion
946
946
  //#region src/official-block-policy.ts
947
- function officialBlockPolicy(context) {
948
- switch (context) {
947
+ function officialBlockPolicy(block) {
948
+ switch (block.getContext?.()) {
949
949
  case "admonition":
950
950
  case "dlist":
951
951
  case "list_item":
@@ -953,12 +953,13 @@ function officialBlockPolicy(context) {
953
953
  case "table":
954
954
  case "verse": return "scan";
955
955
  case "example":
956
- case "olist":
957
956
  case "quote":
958
- case "section":
959
957
  case "sidebar":
960
- case "ulist":
961
- case "open": return "recurse";
958
+ case "open": return block.getContentModel?.() === "simple" ? "scan" : "recurse";
959
+ case "olist":
960
+ case "preamble":
961
+ case "section":
962
+ case "ulist": return "recurse";
962
963
  case "listing":
963
964
  case "literal":
964
965
  case "pass":
@@ -1017,7 +1018,6 @@ function projectOfficialDocument(options) {
1017
1018
  usedAnchorKeys: /* @__PURE__ */ new Set(),
1018
1019
  intervalByBlock: options.intervalByBlock,
1019
1020
  projectableBlocks: options.projectableBlocks,
1020
- containerFallbackBlocks: options.containerFallbackBlocks,
1021
1021
  sectionByBlock: options.sectionByBlock,
1022
1022
  bindingRequests,
1023
1023
  targets,
@@ -1035,19 +1035,18 @@ function buildChildren(officialDocument, context) {
1035
1035
  for (const block of childBlocksOf(officialDocument)) topLevel.push(...toNodes(buildNode(block, context)));
1036
1036
  return topLevel;
1037
1037
  }
1038
- function buildNode(block, context, fallbackLine) {
1038
+ function buildNode(block, context) {
1039
1039
  const blockContext = block.getContext?.();
1040
1040
  const officialLine = block.getSourceLocation?.()?.getLineNumber?.();
1041
- const line = officialLine ?? fallbackLine;
1042
1041
  const interval = context.intervalByBlock.get(block);
1043
- if (!canProjectBlock(block, context, officialLine, fallbackLine)) return;
1042
+ if (!canProjectBlock(block, context)) return;
1044
1043
  if (blockContext === "section" && officialLine !== void 0) return buildSection(block, context);
1045
- if (blockContext === "paragraph" && line !== void 0) return buildParagraph(block, line, context, interval);
1044
+ if (blockContext === "paragraph" && officialLine !== void 0) return buildParagraph(block, officialLine, context, interval);
1046
1045
  if (blockContext === "listing" && officialLine !== void 0) return buildListing(block, officialLine, context, interval);
1047
1046
  if (blockContext === "table" && officialLine !== void 0) return buildTable(block, officialLine, context, interval);
1048
1047
  if (blockContext === "dlist" && officialLine !== void 0) return buildDescriptionList(block, officialLine, context, interval);
1049
- if (blockContext === "open" && line !== void 0) return buildOpenChildren(block, line, context);
1050
- if (officialBlockPolicy(blockContext) === "diagnostic") return;
1048
+ if (blockContext === "open" && officialLine !== void 0) return childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
1049
+ if (officialBlockPolicy(block) === "diagnostic") return;
1051
1050
  if (officialLine !== void 0) {
1052
1051
  registerOfficialBlockTarget(context, block, {
1053
1052
  targetType: "block",
@@ -1059,10 +1058,8 @@ function buildNode(block, context, fallbackLine) {
1059
1058
  return childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
1060
1059
  }
1061
1060
  }
1062
- function canProjectBlock(block, context, officialLine, fallbackLine) {
1063
- if (!context.projectableBlocks) return true;
1064
- if (context.projectableBlocks.has(block)) return true;
1065
- return officialLine === void 0 && fallbackLine !== void 0 && (context.containerFallbackBlocks?.has(block) ?? false);
1061
+ function canProjectBlock(block, context) {
1062
+ return context.projectableBlocks?.has(block) ?? true;
1066
1063
  }
1067
1064
  function buildSection(block, context) {
1068
1065
  const section = context.sectionByBlock.get(block);
@@ -1088,16 +1085,6 @@ function buildSection(block, context) {
1088
1085
  section.children = childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
1089
1086
  return section;
1090
1087
  }
1091
- function buildOpenChildren(block, line, context) {
1092
- const nodes = [];
1093
- let cursor = line;
1094
- for (const child of childBlocksOf(block)) {
1095
- const built = toNodes(buildNode(child, context, child.getSourceLocation?.()?.getLineNumber?.() ?? findBlockSourceLine(context, child, cursor)));
1096
- nodes.push(...built);
1097
- cursor = Math.max(cursor, ...built.map(nodeEndLine).filter(isNumber)) + 1;
1098
- }
1099
- return nodes;
1100
- }
1101
1088
  function buildDescriptionList(block, startLine, context, interval) {
1102
1089
  const metadata = interval?.metadata ?? [];
1103
1090
  const span = interval?.span ?? {
@@ -1329,17 +1316,6 @@ function sourceSpanOf(node) {
1329
1316
  function isLineSpan(value) {
1330
1317
  return value !== void 0;
1331
1318
  }
1332
- function findBlockSourceLine(context, block, startLine) {
1333
- const firstSourceLine = block.getSource?.()?.split(/\r?\n/u).find((line) => line.trim() !== "")?.trim();
1334
- if (!firstSourceLine) return;
1335
- for (let line = startLine; line <= context.lineTable.lines.length; line += 1) if (lineText(context.lineTable, line).trim() === firstSourceLine) return line;
1336
- }
1337
- function nodeEndLine(node) {
1338
- return node.source?.span?.endLine ?? node.source?.line;
1339
- }
1340
- function isNumber(value) {
1341
- return typeof value === "number";
1342
- }
1343
1319
  function buildParagraph(block, line, context, interval) {
1344
1320
  const blockSpan = interval?.span ?? {
1345
1321
  startLine: line,
@@ -1604,6 +1580,37 @@ function isRecord$1(value) {
1604
1580
  return value !== null && typeof value === "object";
1605
1581
  }
1606
1582
  //#endregion
1583
+ //#region src/official-source-location.ts
1584
+ const takenFromParent = /* @__PURE__ */ new WeakSet();
1585
+ const borrowedFromChild = /* @__PURE__ */ new WeakSet();
1586
+ function completeSynthesizedSourceLocations(officialDocument) {
1587
+ for (const block of childBlocksOf(officialDocument)) complete(block, void 0);
1588
+ }
1589
+ function takesParentSourceLocation(block) {
1590
+ return takenFromParent.has(block);
1591
+ }
1592
+ function borrowsChildSourceLocation(block) {
1593
+ return borrowedFromChild.has(block);
1594
+ }
1595
+ function complete(block, parent) {
1596
+ if (!block.getSourceLocation?.() && block.getContext?.() === "paragraph" && parent?.getContext?.() === "open") {
1597
+ locate(block, parent.getSourceLocation?.());
1598
+ takenFromParent.add(block);
1599
+ }
1600
+ const children = childBlocksOf(block);
1601
+ for (const child of children) complete(child, block);
1602
+ if (!block.getSourceLocation?.() && block.getContext?.() === "open") {
1603
+ const location = children.find((child) => child.getSourceLocation?.())?.getSourceLocation?.();
1604
+ if (location) {
1605
+ locate(block, location);
1606
+ borrowedFromChild.add(block);
1607
+ }
1608
+ }
1609
+ }
1610
+ function locate(block, location) {
1611
+ if (location) block.sourceLocation = location;
1612
+ }
1613
+ //#endregion
1607
1614
  //#region src/book-entry/source-scope-index.ts
1608
1615
  const sectionSourceScopeBySection = /* @__PURE__ */ new WeakMap();
1609
1616
  function registerSectionSourceScope(section, scope) {
@@ -1673,7 +1680,7 @@ function scanInlineOccurrencesInOfficialBlocks(options) {
1673
1680
  }
1674
1681
  if (interval.metadataSpan) scanMetadataRange(options.lineTable, interval.metadataSpan.startLine, interval.metadataSpan.endLine, xrefOccurrences, anchorOccurrences);
1675
1682
  const span = interval.contentSpan ?? interval.span;
1676
- if (officialBlockPolicy(surface.context) !== "scan") continue;
1683
+ if (officialBlockPolicy(surface.block) !== "scan") continue;
1677
1684
  if (!scansMacroSubstitutedContent(surface)) continue;
1678
1685
  if (surface.context === "table") scanTableInlineRange(options.lineTable, surface.block, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics);
1679
1686
  else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock));
@@ -1721,7 +1728,7 @@ function recoverSourceAwareInlineOrigins(occurrences, sourceAwareDocument, toolD
1721
1728
  function hasDiagnosticPolicyAncestor$1(surface) {
1722
1729
  let current = surface.parent;
1723
1730
  while (current) {
1724
- if (officialBlockPolicy(current.context) === "diagnostic") return true;
1731
+ if (officialBlockPolicy(current.block) === "diagnostic") return true;
1725
1732
  current = current.parent;
1726
1733
  }
1727
1734
  return false;
@@ -1918,7 +1925,7 @@ function descendantUnscannableRanges(surface, intervalByBlock) {
1918
1925
  return mergeLineSpans(ranges);
1919
1926
  }
1920
1927
  function collectUnscannableRanges(surface, intervalByBlock, ranges) {
1921
- const policy = officialBlockPolicy(surface.context);
1928
+ const policy = officialBlockPolicy(surface.block);
1922
1929
  const interval = intervalByBlock.get(surface.block);
1923
1930
  if (policy === "skip" || policy === "diagnostic") {
1924
1931
  if (interval) ranges.push(interval.span);
@@ -2203,6 +2210,85 @@ function parseStyleIdRoles(part) {
2203
2210
  });
2204
2211
  }
2205
2212
  //#endregion
2213
+ //#region src/official-delimiter.ts
2214
+ const DELIMITED_BLOCKS = {
2215
+ "--": ["open", new Set([
2216
+ "comment",
2217
+ "example",
2218
+ "literal",
2219
+ "listing",
2220
+ "pass",
2221
+ "quote",
2222
+ "sidebar",
2223
+ "source",
2224
+ "verse",
2225
+ "admonition",
2226
+ "abstract",
2227
+ "partintro"
2228
+ ])],
2229
+ "----": ["listing", new Set(["literal", "source"])],
2230
+ "....": ["literal", new Set(["listing", "source"])],
2231
+ "====": ["example", new Set(["admonition"])],
2232
+ "****": ["sidebar", /* @__PURE__ */ new Set()],
2233
+ ____: ["quote", new Set(["verse"])],
2234
+ "++++": ["pass", new Set([
2235
+ "stem",
2236
+ "latexmath",
2237
+ "asciimath"
2238
+ ])],
2239
+ "|===": ["table", /* @__PURE__ */ new Set()],
2240
+ ",===": ["table", /* @__PURE__ */ new Set()],
2241
+ ":===": ["table", /* @__PURE__ */ new Set()],
2242
+ "!===": ["table", /* @__PURE__ */ new Set()],
2243
+ "~~~~": ["open", new Set(["abstract", "partintro"])],
2244
+ "////": ["comment", /* @__PURE__ */ new Set()],
2245
+ "```": ["fenced_code", /* @__PURE__ */ new Set()]
2246
+ };
2247
+ const DELIMITED_BLOCK_HEADS = new Set(Object.keys(DELIMITED_BLOCKS).map((delimiter) => delimiter.slice(0, 2)));
2248
+ const DELIMITED_BLOCK_TAILS = Object.fromEntries(Object.keys(DELIMITED_BLOCKS).filter((delimiter) => delimiter.length === 4).map((delimiter) => [delimiter, delimiter.slice(-1)]));
2249
+ function officialDelimiter(line) {
2250
+ let candidate = line;
2251
+ let lineLength = candidate.length;
2252
+ if (lineLength < 2 || !DELIMITED_BLOCK_HEADS.has(candidate.slice(0, 2))) return;
2253
+ let tip;
2254
+ let tipLength;
2255
+ if (lineLength === 2) {
2256
+ tip = candidate;
2257
+ tipLength = 2;
2258
+ } else {
2259
+ tipLength = lineLength < 5 ? lineLength : 4;
2260
+ tip = candidate.slice(0, tipLength);
2261
+ if (tip.startsWith("`")) {
2262
+ if (tipLength === 4) {
2263
+ if (tip === "````") return;
2264
+ tip = tip.slice(0, 3);
2265
+ if (tip !== "```") return;
2266
+ candidate = tip;
2267
+ lineLength = 3;
2268
+ tipLength = 3;
2269
+ } else if (tip !== "```") return;
2270
+ } else if (tipLength === 3) return;
2271
+ }
2272
+ const entry = DELIMITED_BLOCKS[tip];
2273
+ if (!entry) return;
2274
+ const tail = DELIMITED_BLOCK_TAILS[tip];
2275
+ if (!(lineLength === tipLength || tail !== void 0 && isUniform(candidate.slice(1), tail))) return;
2276
+ const [context, masq] = entry;
2277
+ return {
2278
+ context,
2279
+ masq,
2280
+ terminator: candidate
2281
+ };
2282
+ }
2283
+ function delimiterOpens(delimiter, context, style) {
2284
+ if (context === void 0) return false;
2285
+ if (delimiter.context === "fenced_code") return context === "listing";
2286
+ return delimiter.context === context || delimiter.masq.has(context) || style !== null && style !== void 0 && delimiter.masq.has(style);
2287
+ }
2288
+ function isUniform(text, character) {
2289
+ return [...text].every((each) => each === character);
2290
+ }
2291
+ //#endregion
2206
2292
  //#region src/source-interval-resolver.ts
2207
2293
  function resolveSourceInterval(surface, lineTable) {
2208
2294
  if (surface.sourceLine === void 0) return;
@@ -2210,11 +2296,14 @@ function resolveSourceInterval(surface, lineTable) {
2210
2296
  const metadata = metadataForSurface(surface, lineTable, blockStartLine);
2211
2297
  const startLine = metadata.at(0)?.line ?? blockStartLine;
2212
2298
  const diagnostics = [];
2213
- const delimited = resolveContextDelimitedSpan(lineTable, blockStartLine, surface.context);
2214
- const sourceContentSpan = sourceContentSpanForContext(surface, blockStartLine);
2299
+ const delimitedContext = isDelimitedContext(surface.context);
2300
+ const synthesized = borrowsChildSourceLocation(surface.block);
2301
+ const delimited = delimitedContext ? resolveDelimitedBlockSpan(lineTable, blockStartLine, surface) : void 0;
2302
+ const undelimited = delimitedContext && delimited === void 0 && !synthesized ? resolveUndelimitedBlockSpan(lineTable, blockStartLine, surface) : void 0;
2303
+ const sourceContentSpan = sourceContentSpanForContext(surface, lineTable, blockStartLine);
2215
2304
  const nextSiblingStart = nextSiblingStartLine(surface, lineTable);
2216
2305
  const childEndLine = surface.context === "dlist" ? lastChildEndLine(surface, lineTable) : void 0;
2217
- const endLine = delimited?.span.endLine ?? sourceContentSpan?.endLine ?? childEndLine ?? (nextSiblingStart !== void 0 ? nextSiblingStart - 1 : void 0) ?? parentEndLine(surface, lineTable) ?? lineTable.lines.length;
2306
+ const endLine = delimited?.span.endLine ?? undelimited?.endLine ?? sourceContentSpan?.endLine ?? childEndLine ?? (nextSiblingStart !== void 0 ? nextSiblingStart - 1 : void 0) ?? parentEndLine(surface, lineTable) ?? lineTable.lines.length;
2218
2307
  const span = normalizeLineSpan({
2219
2308
  startLine,
2220
2309
  endLine: Math.max(startLine, endLine)
@@ -2223,13 +2312,13 @@ function resolveSourceInterval(surface, lineTable) {
2223
2312
  startLine,
2224
2313
  endLine: blockStartLine - 1
2225
2314
  } : void 0;
2226
- if (delimited === void 0 && isDelimitedContext(surface.context) && blockStartLine <= lineTable.lines.length) diagnostics.push({
2315
+ if (delimitedContext && delimited === void 0 && undelimited === void 0 && !synthesized && blockStartLine <= lineTable.lines.length) diagnostics.push({
2227
2316
  level: "warning",
2228
2317
  code: "source-interval.delimiter-not-found",
2229
2318
  message: `Could not locate ${surface.context} block delimiter from official source line ${blockStartLine}.`,
2230
2319
  source: spanForLineText(lineTable, blockStartLine, 1)
2231
2320
  });
2232
- const contentSpan = delimited?.contentSpan ?? sourceContentSpan ?? contentSpanForContext(surface, span);
2321
+ const contentSpan = delimited?.contentSpan ?? undelimited ?? sourceContentSpan ?? contentSpanForContext(surface, span);
2233
2322
  const sourceSpan = sourceSpanFromLineSpan(lineTable, span);
2234
2323
  const titleSpan = titleSpanForSurface(surface, lineTable);
2235
2324
  return {
@@ -2251,6 +2340,7 @@ function nextSiblingStartLine(surface, lineTable) {
2251
2340
  }
2252
2341
  function metadataForSurface(surface, lineTable, blockStartLine) {
2253
2342
  if (surface.context === "list_item") return [];
2343
+ if (takesParentSourceLocation(surface.block)) return [];
2254
2344
  return collectPrecedingMetadata(lineTable, blockStartLine);
2255
2345
  }
2256
2346
  function lastChildEndLine(surface, lineTable) {
@@ -2266,25 +2356,10 @@ function parentEndLine(surface, lineTable) {
2266
2356
  if (!parent) return lineTable.lines.length;
2267
2357
  return resolveSourceInterval(parent, lineTable)?.span.endLine;
2268
2358
  }
2269
- function resolveContextDelimitedSpan(lineTable, startLine, context) {
2270
- switch (context) {
2271
- case "example": return resolveDelimitedBlockSpan(lineTable, startLine, ["===="]);
2272
- case "listing": return resolveDelimitedBlockSpan(lineTable, startLine, ["----", "```"]);
2273
- case "literal": return resolveDelimitedBlockSpan(lineTable, startLine, ["...."]);
2274
- case "pass": return resolveDelimitedBlockSpan(lineTable, startLine, ["++++"]);
2275
- case "open": return resolveDelimitedBlockSpan(lineTable, startLine, ["--"]);
2276
- case "quote":
2277
- case "verse": return resolveDelimitedBlockSpan(lineTable, startLine, ["____"]);
2278
- case "sidebar": return resolveDelimitedBlockSpan(lineTable, startLine, ["****"]);
2279
- case "stem": return resolveDelimitedBlockSpan(lineTable, startLine, ["++++"]);
2280
- case "table": return resolveDelimitedBlockSpan(lineTable, startLine, ["|==="]);
2281
- default: return;
2282
- }
2283
- }
2284
- function resolveDelimitedBlockSpan(lineTable, startLine, allowedDelimiters) {
2285
- const opening = lineText(lineTable, startLine).trim();
2286
- if (!allowedDelimiters.includes(opening)) return;
2287
- for (let line = startLine + 1; line <= lineTable.lines.length; line += 1) if (lineText(lineTable, line).trim() === opening) return {
2359
+ function resolveDelimitedBlockSpan(lineTable, startLine, surface) {
2360
+ const delimiter = officialDelimiter(lineText(lineTable, startLine).trimEnd());
2361
+ if (!delimiter || !delimiterOpens(delimiter, surface.context, surface.block.getStyle?.())) return;
2362
+ for (let line = startLine + 1; line <= lineTable.lines.length; line += 1) if (lineText(lineTable, line).trimEnd() === delimiter.terminator) return {
2288
2363
  span: {
2289
2364
  startLine,
2290
2365
  endLine: line
@@ -2295,6 +2370,42 @@ function resolveDelimitedBlockSpan(lineTable, startLine, allowedDelimiters) {
2295
2370
  }
2296
2371
  };
2297
2372
  }
2373
+ function resolveUndelimitedBlockSpan(lineTable, startLine, surface) {
2374
+ const firstLine = firstSourceLine(surface);
2375
+ if (firstLine === void 0 || withoutWhitespace(lineText(lineTable, startLine)) !== withoutWhitespace(firstLine)) return;
2376
+ if (surface.children.length > 0) {
2377
+ const childEnds = surface.children.map((child) => resolveSourceInterval(child, lineTable)?.span.endLine).filter((line) => line !== void 0);
2378
+ return {
2379
+ startLine,
2380
+ endLine: Math.max(startLine, ...childEnds)
2381
+ };
2382
+ }
2383
+ return {
2384
+ startLine,
2385
+ endLine: lastSourceLine(lineTable, startLine, surface.block)
2386
+ };
2387
+ }
2388
+ function withoutWhitespace(text) {
2389
+ return text.replace(/\s+/gu, "");
2390
+ }
2391
+ function lastSourceLine(lineTable, startLine, block) {
2392
+ const count = Math.max(1, block.getSourceLines?.().length ?? block.getSource?.().split(/\r?\n/u).length ?? 1);
2393
+ const skipsLineComments = block.getContentModel?.() !== "verbatim";
2394
+ let counted = 0;
2395
+ for (let line = startLine; line <= lineTable.lines.length; line += 1) {
2396
+ const text = lineText(lineTable, line);
2397
+ if (skipsLineComments && text.startsWith("//") && !text.startsWith("///")) continue;
2398
+ counted += 1;
2399
+ if (counted === count) return line;
2400
+ }
2401
+ return Math.max(startLine, lineTable.lines.length);
2402
+ }
2403
+ function firstSourceLine(surface) {
2404
+ const own = surface.block.getSourceLines?.()[0];
2405
+ if (own !== void 0) return own;
2406
+ const firstChild = surface.children[0];
2407
+ return firstChild ? firstSourceLine(firstChild) : void 0;
2408
+ }
2298
2409
  function contentSpanForContext(surface, span) {
2299
2410
  if (surface.context === "paragraph") return {
2300
2411
  startLine: surface.sourceLine ?? span.startLine,
@@ -2308,13 +2419,12 @@ function contentSpanForContext(surface, span) {
2308
2419
  };
2309
2420
  }
2310
2421
  }
2311
- function sourceContentSpanForContext(surface, startLine) {
2422
+ function sourceContentSpanForContext(surface, lineTable, startLine) {
2312
2423
  if (surface.context !== "paragraph" && surface.context !== "admonition") return;
2313
- const source = surface.block.getSource?.();
2314
- if (source === void 0) return;
2424
+ if (surface.block.getSourceLines === void 0 && surface.block.getSource === void 0) return;
2315
2425
  return {
2316
2426
  startLine,
2317
- endLine: startLine + Math.max(1, source.split(/\r?\n/u).length) - 1
2427
+ endLine: lastSourceLine(lineTable, startLine, surface.block)
2318
2428
  };
2319
2429
  }
2320
2430
  function titleSpanForSurface(surface, lineTable) {
@@ -2357,13 +2467,12 @@ function projectSourceSurfaces(options) {
2357
2467
  const blockSurfaces = walkOfficialBlocks(options.officialDocument);
2358
2468
  const intervalByBlock = /* @__PURE__ */ new WeakMap();
2359
2469
  const projectableBlocks = /* @__PURE__ */ new WeakSet();
2360
- const containerFallbackBlocks = /* @__PURE__ */ new WeakSet();
2361
2470
  const toolDiagnostics = [];
2362
2471
  const mainSourcePath = options.sourcePath ? options.sourceIdentity.normalize(options.sourceIdentity.resolve(options.sourcePath)) : void 0;
2363
2472
  const sourceAwareDocument = sourceAwareDocumentForLineTable(options.lineTable);
2364
2473
  const logicalSource = logicalSourceForLineTable(options.lineTable);
2365
2474
  for (const surface of blockSurfaces) {
2366
- const policy = officialBlockPolicy(surface.context);
2475
+ const policy = officialBlockPolicy(surface.block);
2367
2476
  if (isExternalSourceSurface(surface, mainSourcePath, options.sourceIdentity)) {
2368
2477
  if (policy === "diagnostic") toolDiagnostics.push(unknownContextDiagnostic(surface));
2369
2478
  toolDiagnostics.push({
@@ -2380,7 +2489,6 @@ function projectSourceSurfaces(options) {
2380
2489
  code: "source-location.missing",
2381
2490
  message: `Official block source location is missing for context '${surface.context ?? "undefined"}' node '${surface.nodeName ?? "undefined"}'.`
2382
2491
  });
2383
- if (canUseContainerFallbackSurface(surface, policy)) containerFallbackBlocks.add(surface.block);
2384
2492
  continue;
2385
2493
  }
2386
2494
  const interval = resolveSourceInterval(surface, options.lineTable);
@@ -2424,7 +2532,6 @@ function projectSourceSurfaces(options) {
2424
2532
  blockSurfaces,
2425
2533
  intervalByBlock,
2426
2534
  projectableBlocks,
2427
- containerFallbackBlocks,
2428
2535
  sectionByBlock,
2429
2536
  sections,
2430
2537
  xrefOccurrences,
@@ -2457,9 +2564,6 @@ function unknownContextDiagnostic(surface, interval) {
2457
2564
  source: interval?.sourceSpan
2458
2565
  });
2459
2566
  }
2460
- function canUseContainerFallbackSurface(surface, policy) {
2461
- return surface.context === "paragraph" && policy === "scan" && !hasDiagnosticPolicyAncestor(surface);
2462
- }
2463
2567
  function buildSectionSurfaces(blockSurfaces, intervalByBlock, lineTable, toolDiagnostics, logicalSource, sourceAwareDocument) {
2464
2568
  const sections = [];
2465
2569
  const sectionByBlock = /* @__PURE__ */ new WeakMap();
@@ -2577,7 +2681,7 @@ function sourceAwareOriginForLine(document, logicalLine) {
2577
2681
  function hasDiagnosticPolicyAncestor(surface) {
2578
2682
  let current = surface.parent;
2579
2683
  while (current) {
2580
- if (officialBlockPolicy(current.context) === "diagnostic") return true;
2684
+ if (officialBlockPolicy(current.block) === "diagnostic") return true;
2581
2685
  current = current.parent;
2582
2686
  }
2583
2687
  return false;
@@ -2594,6 +2698,7 @@ function mapSectionScope(sections, totalLines) {
2594
2698
  //#endregion
2595
2699
  //#region src/parser-core.ts
2596
2700
  async function parseAsciidoctorDocument(input) {
2701
+ completeSynthesizedSourceLocations(input.officialDocument);
2597
2702
  const sourceSurfaces = projectSourceSurfaces({
2598
2703
  officialDocument: input.officialDocument,
2599
2704
  lineTable: input.lineTable,
@@ -2610,7 +2715,6 @@ async function parseAsciidoctorDocument(input) {
2610
2715
  headingInlineMetadataOccurrences: sourceSurfaces.headingInlineMetadataOccurrences,
2611
2716
  intervalByBlock: sourceSurfaces.intervalByBlock,
2612
2717
  projectableBlocks: sourceSurfaces.projectableBlocks,
2613
- containerFallbackBlocks: sourceSurfaces.containerFallbackBlocks,
2614
2718
  sectionByBlock: sourceSurfaces.sectionByBlock
2615
2719
  });
2616
2720
  await resolveOfficialBindings(officialProjection.bindingRequests, input.officialDocument, input.adapter);
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, t as rdf12 } from "./src-CNTTQlPJ.mjs";
2
+ import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, t as rdf12 } from "./src-DMOLXZbF.mjs";
3
3
  import { relative, resolve, sep } from "node:path";
4
4
  import { existsSync } from "node:fs";
5
5
  import process from "node:process";
package/dist/index.d.mts CHANGED
@@ -213,6 +213,23 @@ type ParseAbundantTreeOptions = {
213
213
  sourcePath: string;
214
214
  mode: "book-entry";
215
215
  documentRoot: string;
216
+ includeResolvers?: readonly IncludeResolver[];
217
+ };
218
+ type IncludeResolver = {
219
+ readonly prefix: string;
220
+ resolve(request: IncludeResolverRequest): IncludeResolution | Promise<IncludeResolution>;
221
+ };
222
+ type IncludeResolverRequest = {
223
+ readonly target: string;
224
+ readonly attributes: Readonly<Record<string, string>>;
225
+ readonly sourcePath: string;
226
+ };
227
+ type IncludeResolution = {
228
+ readonly text: string;
229
+ readonly origin: {
230
+ readonly path: string;
231
+ readonly startLine: number;
232
+ };
216
233
  };
217
234
  //#endregion
218
235
  //#region src/rdf12-projection/options.d.ts
@@ -354,4 +371,4 @@ type Rdf12Result = Rdf12Projection & {
354
371
  };
355
372
  declare function rdf12(document: AbundantDocument, options: Rdf12Options): Rdf12Result;
356
373
  //#endregion
357
- export { type AbundantDocument, type AbundantNode, type AnchorOccurrenceNode, type AsciidoctorLayer, type ExportAnimationYamlOptions, type ExportAnimationYamlResult, type LineSpan, type ListingNode, type MetadataNode, type OutputFormat, type ParagraphNode, type ParseAbundantTreeOptions, type ParserInfo, Rdf12Result, type SectionNode, type SourceFileNode, type SourceLayer, type SourcePosition, type SourceSpan, type TableNode, type TargetNode, type TargetType, type TextNode, type TitleNode, type ToolDiagnostic, type XrefOccurrenceNode, exportAnimationYaml, formatAbundantTree, parseAbundantTree, rdf12, serializeAbundantTree, serializeAbundantTreeToJson };
374
+ export { type AbundantDocument, type AbundantNode, type AnchorOccurrenceNode, type AsciidoctorLayer, type ExportAnimationYamlOptions, type ExportAnimationYamlResult, type IncludeResolution, type IncludeResolver, type IncludeResolverRequest, type LineSpan, type ListingNode, type MetadataNode, type OutputFormat, type ParagraphNode, type ParseAbundantTreeOptions, type ParserInfo, Rdf12Result, type SectionNode, type SourceFileNode, type SourceLayer, type SourcePosition, type SourceSpan, type TableNode, type TargetNode, type TargetType, type TextNode, type TitleNode, type ToolDiagnostic, type XrefOccurrenceNode, exportAnimationYaml, formatAbundantTree, parseAbundantTree, rdf12, serializeAbundantTree, serializeAbundantTreeToJson };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, o as exportAnimationYaml, r as serializeAbundantTree, t as rdf12 } from "./src-CNTTQlPJ.mjs";
1
+ import { a as parseAbundantTree, i as serializeAbundantTreeToJson, n as formatAbundantTree, o as exportAnimationYaml, r as serializeAbundantTree, t as rdf12 } from "./src-DMOLXZbF.mjs";
2
2
  export { exportAnimationYaml, formatAbundantTree, parseAbundantTree, rdf12, serializeAbundantTree, serializeAbundantTreeToJson };
@@ -2544,6 +2544,90 @@ function isElement(node) {
2544
2544
  return "tagName" in node;
2545
2545
  }
2546
2546
  //#endregion
2547
+ //#region src/book-entry/diagnostics.ts
2548
+ var BookEntryConstructionError = class extends Error {
2549
+ code;
2550
+ diagnostic;
2551
+ constructor(diagnostic) {
2552
+ super(`${diagnostic.code}: ${diagnostic.message}`);
2553
+ this.name = "BookEntryConstructionError";
2554
+ this.code = diagnostic.code;
2555
+ this.diagnostic = diagnostic;
2556
+ }
2557
+ };
2558
+ function constructionError(code, message, source) {
2559
+ return new BookEntryConstructionError({
2560
+ level: "error",
2561
+ code,
2562
+ message,
2563
+ ...source ? { source } : {}
2564
+ });
2565
+ }
2566
+ //#endregion
2567
+ //#region src/book-entry/path-boundary.ts
2568
+ function normalizeDocumentRoot(documentRoot) {
2569
+ return resolve(documentRoot);
2570
+ }
2571
+ function resolveEntryPath(sourcePath) {
2572
+ return resolve(sourcePath);
2573
+ }
2574
+ function resolveIncludeTarget(containingFilePath, targetPath) {
2575
+ return resolve(containingFilePath, "..", targetPath);
2576
+ }
2577
+ function relativePathWithinDocumentRoot(documentRoot, absolutePath) {
2578
+ const normalizedRoot = normalizeDocumentRoot(documentRoot);
2579
+ const normalizedPath = resolve(absolutePath);
2580
+ const relativePath = relative(normalizedRoot, normalizedPath);
2581
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativePath.includes(`${sep}..${sep}`) || relativePath.startsWith(`${sep}`)) throw constructionError("include.outside-document-root", `Resolved include path is outside documentRoot: ${normalizedPath}.`);
2582
+ return toPortableRelativePath(relativePath);
2583
+ }
2584
+ function toPortableRelativePath(value) {
2585
+ return value.split(sep).join("/");
2586
+ }
2587
+ //#endregion
2588
+ //#region src/book-entry/include-resolvers.ts
2589
+ function claimingResolver(resolvers, target) {
2590
+ return resolvers.find((resolver) => target.startsWith(resolver.prefix));
2591
+ }
2592
+ function registerIncludeResolvers(registry, resolvers, documentRoot, resolutions) {
2593
+ registry.includeProcessor(function() {
2594
+ this.handles((target) => claimingResolver(resolvers, target) !== void 0);
2595
+ this.process(async (_document, reader, target, attributes) => {
2596
+ const resolver = claimingResolver(resolvers, target);
2597
+ if (!resolver) return;
2598
+ const sourcePath = reader.getCursor().getFile() ?? "";
2599
+ const resolution = await resolveOrFail(resolver, target, attributes, sourcePath);
2600
+ const absolutePath = resolve(documentRoot, resolution.origin.path);
2601
+ let relativePath;
2602
+ try {
2603
+ relativePath = relativePathWithinDocumentRoot(documentRoot, absolutePath);
2604
+ } catch {
2605
+ throw constructionError("include.outside-document-root", `Include resolver '${resolver.prefix}' returned a source outside documentRoot for include::${target}[] in ${sourcePath}: ${absolutePath}.`);
2606
+ }
2607
+ const text = resolution.text.replace(/\r?\n$/u, "");
2608
+ reader.pushInclude(text, absolutePath, relativePath, resolution.origin.startLine, attributes);
2609
+ const frame = reader.includeStack.at(-1);
2610
+ if (frame && text !== "") resolutions.set(frame, {
2611
+ target,
2612
+ absolutePath,
2613
+ startLine: resolution.origin.startLine,
2614
+ lineCount: text.split(/\r?\n/u).length
2615
+ });
2616
+ });
2617
+ });
2618
+ }
2619
+ async function resolveOrFail(resolver, target, attributes, sourcePath) {
2620
+ try {
2621
+ return await resolver.resolve({
2622
+ target: target.slice(resolver.prefix.length),
2623
+ attributes,
2624
+ sourcePath
2625
+ });
2626
+ } catch (error) {
2627
+ throw constructionError("include.resolver-failed", `Include resolver '${resolver.prefix}' failed for include::${target}[] in ${sourcePath}: ${error instanceof Error ? error.message : String(error)}.`);
2628
+ }
2629
+ }
2630
+ //#endregion
2547
2631
  //#region src/asciidoctor-file-adapter.ts
2548
2632
  function createAsciidoctorFileAdapter() {
2549
2633
  const extensionRegistry = createExtensionRegistry();
@@ -2558,6 +2642,9 @@ function createAsciidoctorFileAdapter() {
2558
2642
  });
2559
2643
  },
2560
2644
  async readPreprocessedLines(options) {
2645
+ const resolutions = /* @__PURE__ */ new WeakMap();
2646
+ const registry = Extensions.create();
2647
+ if (options.includeResolution) registerIncludeResolvers(registry, options.includeResolution.resolvers, options.includeResolution.documentRoot, resolutions);
2561
2648
  const reader = (await loadFile(options.sourcePath, {
2562
2649
  safe: "unsafe",
2563
2650
  base_dir: options.baseDir,
@@ -2565,7 +2652,7 @@ function createAsciidoctorFileAdapter() {
2565
2652
  parse: false,
2566
2653
  to_file: false,
2567
2654
  attributes: options.attributes ?? {},
2568
- extension_registry: extensionRegistry
2655
+ extension_registry: registry
2569
2656
  })).getReader();
2570
2657
  const lines = [];
2571
2658
  const pushIds = /* @__PURE__ */ new WeakMap();
@@ -2581,12 +2668,14 @@ function createAsciidoctorFileAdapter() {
2581
2668
  pushes += 1;
2582
2669
  pushIds.set(frame, pushes);
2583
2670
  }
2671
+ const resolved = frame ? resolutions.get(frame) : void 0;
2584
2672
  lines.push({
2585
2673
  text,
2586
2674
  cursor: cursorForReturnedReaderLine(before, after),
2587
2675
  push: {
2588
2676
  id: frame ? pushIds.get(frame) ?? 0 : 0,
2589
- depth: stack.length
2677
+ depth: stack.length,
2678
+ ...resolved ? { resolved } : {}
2590
2679
  }
2591
2680
  });
2592
2681
  }
@@ -2619,26 +2708,6 @@ function cursorForReturnedReaderLine(before, after) {
2619
2708
  return before;
2620
2709
  }
2621
2710
  //#endregion
2622
- //#region src/book-entry/diagnostics.ts
2623
- var BookEntryConstructionError = class extends Error {
2624
- code;
2625
- diagnostic;
2626
- constructor(diagnostic) {
2627
- super(`${diagnostic.code}: ${diagnostic.message}`);
2628
- this.name = "BookEntryConstructionError";
2629
- this.code = diagnostic.code;
2630
- this.diagnostic = diagnostic;
2631
- }
2632
- };
2633
- function constructionError(code, message, source) {
2634
- return new BookEntryConstructionError({
2635
- level: "error",
2636
- code,
2637
- message,
2638
- ...source ? { source } : {}
2639
- });
2640
- }
2641
- //#endregion
2642
2711
  //#region src/book-entry/include-attribute-model.ts
2643
2712
  const mappedAttributeNames = new Set([
2644
2713
  "tag",
@@ -2919,27 +2988,6 @@ function buildParserSafeLogicalText(lineRecords) {
2919
2988
  };
2920
2989
  }
2921
2990
  //#endregion
2922
- //#region src/book-entry/path-boundary.ts
2923
- function normalizeDocumentRoot(documentRoot) {
2924
- return resolve(documentRoot);
2925
- }
2926
- function resolveEntryPath(sourcePath) {
2927
- return resolve(sourcePath);
2928
- }
2929
- function resolveIncludeTarget(containingFilePath, targetPath) {
2930
- return resolve(containingFilePath, "..", targetPath);
2931
- }
2932
- function relativePathWithinDocumentRoot(documentRoot, absolutePath) {
2933
- const normalizedRoot = normalizeDocumentRoot(documentRoot);
2934
- const normalizedPath = resolve(absolutePath);
2935
- const relativePath = relative(normalizedRoot, normalizedPath);
2936
- if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || relativePath.includes(`${sep}..${sep}`) || relativePath.startsWith(`${sep}`)) throw constructionError("include.outside-document-root", `Resolved include path is outside documentRoot: ${normalizedPath}.`);
2937
- return toPortableRelativePath(relativePath);
2938
- }
2939
- function toPortableRelativePath(value) {
2940
- return value.split(sep).join("/");
2941
- }
2942
- //#endregion
2943
2991
  //#region src/book-entry/reader-boundary-policy.ts
2944
2992
  function readerPreprocessingOptions(options) {
2945
2993
  const sourcePath = resolve(options.sourcePath);
@@ -3025,13 +3073,14 @@ function createSourceSet(options) {
3025
3073
  //#endregion
3026
3074
  //#region src/book-entry/official-reader-preprocessor.ts
3027
3075
  const asciidocExtensionPattern = /\.(?:adoc|asciidoc|asc|ad|txt)$/u;
3028
- function validateIncludeGraph(absolutePath, documentRoot, stack) {
3076
+ function validateIncludeGraph(absolutePath, documentRoot, stack, includeResolvers) {
3029
3077
  const cycleStart = stack.indexOf(absolutePath);
3030
3078
  if (cycleStart !== -1) throw constructionError("include.cycle", `Include graph contains a cycle: ${[...stack.slice(cycleStart), absolutePath].join(" -> ")}.`);
3031
3079
  const nextStack = [...stack, absolutePath];
3032
3080
  for (const line of authoredTextLines(readFileSync(absolutePath, "utf8"))) {
3033
3081
  const directive = parseReaderIncludeDirective(line);
3034
3082
  if (!directive || directive.escaped) continue;
3083
+ if (claimingResolver(includeResolvers, directive.target)) continue;
3035
3084
  const attributes = parseIncludeAttributes(directive.attrlist);
3036
3085
  if (attributes.classification === "unmapped") continue;
3037
3086
  if (isUriTarget(directive.target) || directive.target.includes("{")) throwDiagnostic(classifyReaderBoundaryDiagnostic({
@@ -3064,7 +3113,7 @@ function validateIncludeGraph(absolutePath, documentRoot, stack) {
3064
3113
  }));
3065
3114
  }
3066
3115
  if (selectionSurface(attributes) || !asciidocExtensionPattern.test(includePath)) continue;
3067
- validateIncludeGraph(includePath, documentRoot, nextStack);
3116
+ validateIncludeGraph(includePath, documentRoot, nextStack, includeResolvers);
3068
3117
  }
3069
3118
  }
3070
3119
  async function preprocessBookEntryWithOfficialReader(options) {
@@ -3072,13 +3121,21 @@ async function preprocessBookEntryWithOfficialReader(options) {
3072
3121
  const documentRoot = normalizeDocumentRoot(options.documentRoot);
3073
3122
  relativePathWithinDocumentRoot(documentRoot, entryPath);
3074
3123
  if (!existsSync(entryPath)) throw constructionError("entry.missing-source", `Entry source file does not exist: ${entryPath}.`);
3075
- validateIncludeGraph(entryPath, documentRoot, []);
3124
+ const includeResolvers = options.includeResolvers ?? [];
3125
+ validateIncludeGraph(entryPath, documentRoot, [], includeResolvers);
3076
3126
  const state = {
3077
3127
  documentRoot,
3078
- officialLines: await options.adapter.readPreprocessedLines(readerPreprocessingOptions({
3079
- sourcePath: entryPath,
3080
- attributes: options.attributes
3081
- })),
3128
+ includeResolvers,
3129
+ officialLines: await options.adapter.readPreprocessedLines({
3130
+ ...readerPreprocessingOptions({
3131
+ sourcePath: entryPath,
3132
+ attributes: options.attributes
3133
+ }),
3134
+ includeResolution: {
3135
+ resolvers: includeResolvers,
3136
+ documentRoot
3137
+ }
3138
+ }),
3082
3139
  sourceSet: createSourceSet({ documentRoot }),
3083
3140
  records: [],
3084
3141
  diagnostics: [],
@@ -3128,6 +3185,10 @@ function appendSourceFile(absolutePath, state) {
3128
3185
  }
3129
3186
  }
3130
3187
  function appendIncludeDirective(containingFile, line, directive, state) {
3188
+ if (claimingResolver(state.includeResolvers, directive.target)) {
3189
+ appendResolvedInclude(containingFile, line, directive, state);
3190
+ return;
3191
+ }
3131
3192
  const attributes = parseIncludeAttributes(directive.attrlist);
3132
3193
  state.diagnostics.push(...attributes.diagnostics);
3133
3194
  if (attributes.classification === "unmapped") {
@@ -3166,18 +3227,40 @@ function appendIncludeDirective(containingFile, line, directive, state) {
3166
3227
  }
3167
3228
  const targetFile = state.sourceSet.registerFile(includePath);
3168
3229
  const selector = selectionSurface(attributes);
3230
+ appendPushedInclude(containingFile, line, targetFile, attributes, selector ? {
3231
+ selector,
3232
+ selection: mapIncludeSelection(targetFile.text, selector)
3233
+ } : void 0, state);
3234
+ }
3235
+ function appendResolvedInclude(containingFile, line, directive, state) {
3236
+ const first = state.officialLines[state.officialIndex];
3237
+ const resolved = first && first.push.depth === state.currentPush.depth + 1 ? first.push.resolved : void 0;
3238
+ if (!resolved || resolved.target !== directive.target) return;
3239
+ appendPushedInclude(containingFile, line, state.sourceSet.registerFile(resolved.absolutePath), parseIncludeAttributes(directive.attrlist), {
3240
+ selector: {
3241
+ kind: "resolver",
3242
+ target: directive.target
3243
+ },
3244
+ selection: {
3245
+ ok: true,
3246
+ lines: Array.from({ length: resolved.lineCount }, (_, index) => resolved.startLine + index)
3247
+ }
3248
+ }, state);
3249
+ }
3250
+ function appendPushedInclude(containingFile, line, targetFile, attributes, selected, state) {
3251
+ const includePath = targetFile.absolutePath;
3169
3252
  const leveloffset = hasLeveloffsetSurface(attributes);
3170
3253
  const parent = state.currentPush;
3171
3254
  const first = state.officialLines[state.officialIndex];
3172
- const selection = selector ? mapIncludeSelection(targetFile.text, selector) : void 0;
3255
+ const selection = selected?.selection;
3173
3256
  const candidate = first && first.push.depth === parent.depth + 1 && selection?.ok !== false ? first.push : void 0;
3174
3257
  const own = candidate && state.officialLines.slice(state.officialIndex, pushEnd(candidate, state)).some((official) => official.push.id === candidate.id && official.cursor.file === includePath) ? candidate : void 0;
3175
3258
  const end = own ? pushEnd(own, state) : state.officialIndex;
3176
3259
  state.currentPush = own ?? parent;
3177
3260
  consumeGeneratedPreludeForInclude(leveloffset, own, state);
3178
- if (selector && selection) appendSelectedInclude(targetFile, selector, indentSurface(attributes), state, `${containingFile.relativePath}:${line.number}`, selection, own, leveloffset ? generatedTailStart(end, state) : end);
3261
+ if (selected) appendSelectedInclude(targetFile, selected.selector, indentSurface(attributes), state, `${containingFile.relativePath}:${line.number}`, selected.selection, own, leveloffset ? generatedTailStart(end, state) : end);
3179
3262
  else appendFullFileInclude(targetFile, indentSurface(attributes), state);
3180
- if (leveloffset && own) consumeGeneratedTailForInclude(state);
3263
+ if (leveloffset && own) consumeGeneratedTailForInclude(own, state);
3181
3264
  state.currentPush = parent;
3182
3265
  }
3183
3266
  function pushEnd(own, state) {
@@ -3265,7 +3348,11 @@ function degradeRestOfPush(targetFile, selector, includeSite, bodyEnd, state) {
3265
3348
  });
3266
3349
  }
3267
3350
  function selectorSurface(selector) {
3268
- return selector.kind === "tag" ? `tag=${selector.name}` : `${selector.kind}=${selector.expression}`;
3351
+ switch (selector.kind) {
3352
+ case "tag": return `tag=${selector.name}`;
3353
+ case "resolver": return `the resolver for ${selector.target}`;
3354
+ default: return `${selector.kind}=${selector.expression}`;
3355
+ }
3269
3356
  }
3270
3357
  function sameLine(officialText, sourceText, indent) {
3271
3358
  const official = officialText.trimEnd();
@@ -3348,15 +3435,13 @@ function consumeGeneratedPreludeForInclude(hasLeveloffset, own, state) {
3348
3435
  const blank = state.officialLines[state.officialIndex];
3349
3436
  if (blank?.push.id === own.id && blank.text === "") appendGeneratedControl(consumeOfficialLine(state)?.text ?? "", state);
3350
3437
  }
3351
- function consumeGeneratedTailForInclude(state) {
3438
+ function consumeGeneratedTailForInclude(own, state) {
3352
3439
  while (state.officialIndex < state.officialLines.length) {
3353
3440
  const next = state.officialLines[state.officialIndex];
3354
- if (!next || !(next.text === "" || next.text.startsWith(":leveloffset"))) break;
3355
- const official = consumeOfficialLine(state);
3356
- if (official?.text.startsWith(":leveloffset")) {
3357
- appendGeneratedControl(official.text, state);
3358
- break;
3359
- }
3441
+ if (!next || next.push.id !== own.id || !(next.text === "" || next.text.startsWith(":leveloffset"))) break;
3442
+ consumeOfficialLine(state);
3443
+ appendGeneratedControl(next.text, state);
3444
+ if (next.text.startsWith(":leveloffset")) break;
3360
3445
  }
3361
3446
  }
3362
3447
  function consumeUnmappedOfficialTail(state) {
@@ -3853,8 +3938,8 @@ function recoveryFailure(code, message, source) {
3853
3938
  }
3854
3939
  //#endregion
3855
3940
  //#region src/official-block-policy.ts
3856
- function officialBlockPolicy(context) {
3857
- switch (context) {
3941
+ function officialBlockPolicy(block) {
3942
+ switch (block.getContext?.()) {
3858
3943
  case "admonition":
3859
3944
  case "dlist":
3860
3945
  case "list_item":
@@ -3862,12 +3947,13 @@ function officialBlockPolicy(context) {
3862
3947
  case "table":
3863
3948
  case "verse": return "scan";
3864
3949
  case "example":
3865
- case "olist":
3866
3950
  case "quote":
3867
- case "section":
3868
3951
  case "sidebar":
3869
- case "ulist":
3870
- case "open": return "recurse";
3952
+ case "open": return block.getContentModel?.() === "simple" ? "scan" : "recurse";
3953
+ case "olist":
3954
+ case "preamble":
3955
+ case "section":
3956
+ case "ulist": return "recurse";
3871
3957
  case "listing":
3872
3958
  case "literal":
3873
3959
  case "pass":
@@ -3926,7 +4012,6 @@ function projectOfficialDocument(options) {
3926
4012
  usedAnchorKeys: /* @__PURE__ */ new Set(),
3927
4013
  intervalByBlock: options.intervalByBlock,
3928
4014
  projectableBlocks: options.projectableBlocks,
3929
- containerFallbackBlocks: options.containerFallbackBlocks,
3930
4015
  sectionByBlock: options.sectionByBlock,
3931
4016
  bindingRequests,
3932
4017
  targets,
@@ -3944,19 +4029,18 @@ function buildChildren(officialDocument, context) {
3944
4029
  for (const block of childBlocksOf(officialDocument)) topLevel.push(...toNodes(buildNode(block, context)));
3945
4030
  return topLevel;
3946
4031
  }
3947
- function buildNode(block, context, fallbackLine) {
4032
+ function buildNode(block, context) {
3948
4033
  const blockContext = block.getContext?.();
3949
4034
  const officialLine = block.getSourceLocation?.()?.getLineNumber?.();
3950
- const line = officialLine ?? fallbackLine;
3951
4035
  const interval = context.intervalByBlock.get(block);
3952
- if (!canProjectBlock(block, context, officialLine, fallbackLine)) return;
4036
+ if (!canProjectBlock(block, context)) return;
3953
4037
  if (blockContext === "section" && officialLine !== void 0) return buildSection(block, context);
3954
- if (blockContext === "paragraph" && line !== void 0) return buildParagraph(block, line, context, interval);
4038
+ if (blockContext === "paragraph" && officialLine !== void 0) return buildParagraph(block, officialLine, context, interval);
3955
4039
  if (blockContext === "listing" && officialLine !== void 0) return buildListing(block, officialLine, context, interval);
3956
4040
  if (blockContext === "table" && officialLine !== void 0) return buildTable(block, officialLine, context, interval);
3957
4041
  if (blockContext === "dlist" && officialLine !== void 0) return buildDescriptionList(block, officialLine, context, interval);
3958
- if (blockContext === "open" && line !== void 0) return buildOpenChildren(block, line, context);
3959
- if (officialBlockPolicy(blockContext) === "diagnostic") return;
4042
+ if (blockContext === "open" && officialLine !== void 0) return childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
4043
+ if (officialBlockPolicy(block) === "diagnostic") return;
3960
4044
  if (officialLine !== void 0) {
3961
4045
  registerOfficialBlockTarget(context, block, {
3962
4046
  targetType: "block",
@@ -3968,10 +4052,8 @@ function buildNode(block, context, fallbackLine) {
3968
4052
  return childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
3969
4053
  }
3970
4054
  }
3971
- function canProjectBlock(block, context, officialLine, fallbackLine) {
3972
- if (!context.projectableBlocks) return true;
3973
- if (context.projectableBlocks.has(block)) return true;
3974
- return officialLine === void 0 && fallbackLine !== void 0 && (context.containerFallbackBlocks?.has(block) ?? false);
4055
+ function canProjectBlock(block, context) {
4056
+ return context.projectableBlocks?.has(block) ?? true;
3975
4057
  }
3976
4058
  function buildSection(block, context) {
3977
4059
  const section = context.sectionByBlock.get(block);
@@ -3997,16 +4079,6 @@ function buildSection(block, context) {
3997
4079
  section.children = childBlocksOf(block).flatMap((child) => toNodes(buildNode(child, context)));
3998
4080
  return section;
3999
4081
  }
4000
- function buildOpenChildren(block, line, context) {
4001
- const nodes = [];
4002
- let cursor = line;
4003
- for (const child of childBlocksOf(block)) {
4004
- const built = toNodes(buildNode(child, context, child.getSourceLocation?.()?.getLineNumber?.() ?? findBlockSourceLine(context, child, cursor)));
4005
- nodes.push(...built);
4006
- cursor = Math.max(cursor, ...built.map(nodeEndLine).filter(isNumber)) + 1;
4007
- }
4008
- return nodes;
4009
- }
4010
4082
  function buildDescriptionList(block, startLine, context, interval) {
4011
4083
  const metadata = interval?.metadata ?? [];
4012
4084
  const span = interval?.span ?? {
@@ -4238,17 +4310,6 @@ function sourceSpanOf(node) {
4238
4310
  function isLineSpan(value) {
4239
4311
  return value !== void 0;
4240
4312
  }
4241
- function findBlockSourceLine(context, block, startLine) {
4242
- const firstSourceLine = block.getSource?.()?.split(/\r?\n/u).find((line) => line.trim() !== "")?.trim();
4243
- if (!firstSourceLine) return;
4244
- for (let line = startLine; line <= context.lineTable.lines.length; line += 1) if (lineText(context.lineTable, line).trim() === firstSourceLine) return line;
4245
- }
4246
- function nodeEndLine(node) {
4247
- return node.source?.span?.endLine ?? node.source?.line;
4248
- }
4249
- function isNumber(value) {
4250
- return typeof value === "number";
4251
- }
4252
4313
  function buildParagraph(block, line, context, interval) {
4253
4314
  const blockSpan = interval?.span ?? {
4254
4315
  startLine: line,
@@ -4513,6 +4574,37 @@ function isRecord$1(value) {
4513
4574
  return value !== null && typeof value === "object";
4514
4575
  }
4515
4576
  //#endregion
4577
+ //#region src/official-source-location.ts
4578
+ const takenFromParent = /* @__PURE__ */ new WeakSet();
4579
+ const borrowedFromChild = /* @__PURE__ */ new WeakSet();
4580
+ function completeSynthesizedSourceLocations(officialDocument) {
4581
+ for (const block of childBlocksOf(officialDocument)) complete(block, void 0);
4582
+ }
4583
+ function takesParentSourceLocation(block) {
4584
+ return takenFromParent.has(block);
4585
+ }
4586
+ function borrowsChildSourceLocation(block) {
4587
+ return borrowedFromChild.has(block);
4588
+ }
4589
+ function complete(block, parent) {
4590
+ if (!block.getSourceLocation?.() && block.getContext?.() === "paragraph" && parent?.getContext?.() === "open") {
4591
+ locate(block, parent.getSourceLocation?.());
4592
+ takenFromParent.add(block);
4593
+ }
4594
+ const children = childBlocksOf(block);
4595
+ for (const child of children) complete(child, block);
4596
+ if (!block.getSourceLocation?.() && block.getContext?.() === "open") {
4597
+ const location = children.find((child) => child.getSourceLocation?.())?.getSourceLocation?.();
4598
+ if (location) {
4599
+ locate(block, location);
4600
+ borrowedFromChild.add(block);
4601
+ }
4602
+ }
4603
+ }
4604
+ function locate(block, location) {
4605
+ if (location) block.sourceLocation = location;
4606
+ }
4607
+ //#endregion
4516
4608
  //#region src/book-entry/source-scope-index.ts
4517
4609
  const sectionSourceScopeBySection = /* @__PURE__ */ new WeakMap();
4518
4610
  function registerSectionSourceScope(section, scope) {
@@ -4582,7 +4674,7 @@ function scanInlineOccurrencesInOfficialBlocks(options) {
4582
4674
  }
4583
4675
  if (interval.metadataSpan) scanMetadataRange(options.lineTable, interval.metadataSpan.startLine, interval.metadataSpan.endLine, xrefOccurrences, anchorOccurrences);
4584
4676
  const span = interval.contentSpan ?? interval.span;
4585
- if (officialBlockPolicy(surface.context) !== "scan") continue;
4677
+ if (officialBlockPolicy(surface.block) !== "scan") continue;
4586
4678
  if (!scansMacroSubstitutedContent(surface)) continue;
4587
4679
  if (surface.context === "table") scanTableInlineRange(options.lineTable, surface.block, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics);
4588
4680
  else scanInlineRange(options.lineTable, span.startLine, span.endLine, xrefOccurrences, anchorOccurrences, headingInlineMetadataOccurrences, options.toolDiagnostics, descendantUnscannableRanges(surface, options.intervalByBlock));
@@ -4630,7 +4722,7 @@ function recoverSourceAwareInlineOrigins(occurrences, sourceAwareDocument, toolD
4630
4722
  function hasDiagnosticPolicyAncestor$1(surface) {
4631
4723
  let current = surface.parent;
4632
4724
  while (current) {
4633
- if (officialBlockPolicy(current.context) === "diagnostic") return true;
4725
+ if (officialBlockPolicy(current.block) === "diagnostic") return true;
4634
4726
  current = current.parent;
4635
4727
  }
4636
4728
  return false;
@@ -4827,7 +4919,7 @@ function descendantUnscannableRanges(surface, intervalByBlock) {
4827
4919
  return mergeLineSpans(ranges);
4828
4920
  }
4829
4921
  function collectUnscannableRanges(surface, intervalByBlock, ranges) {
4830
- const policy = officialBlockPolicy(surface.context);
4922
+ const policy = officialBlockPolicy(surface.block);
4831
4923
  const interval = intervalByBlock.get(surface.block);
4832
4924
  if (policy === "skip" || policy === "diagnostic") {
4833
4925
  if (interval) ranges.push(interval.span);
@@ -5112,6 +5204,85 @@ function parseStyleIdRoles(part) {
5112
5204
  });
5113
5205
  }
5114
5206
  //#endregion
5207
+ //#region src/official-delimiter.ts
5208
+ const DELIMITED_BLOCKS = {
5209
+ "--": ["open", new Set([
5210
+ "comment",
5211
+ "example",
5212
+ "literal",
5213
+ "listing",
5214
+ "pass",
5215
+ "quote",
5216
+ "sidebar",
5217
+ "source",
5218
+ "verse",
5219
+ "admonition",
5220
+ "abstract",
5221
+ "partintro"
5222
+ ])],
5223
+ "----": ["listing", new Set(["literal", "source"])],
5224
+ "....": ["literal", new Set(["listing", "source"])],
5225
+ "====": ["example", new Set(["admonition"])],
5226
+ "****": ["sidebar", /* @__PURE__ */ new Set()],
5227
+ ____: ["quote", new Set(["verse"])],
5228
+ "++++": ["pass", new Set([
5229
+ "stem",
5230
+ "latexmath",
5231
+ "asciimath"
5232
+ ])],
5233
+ "|===": ["table", /* @__PURE__ */ new Set()],
5234
+ ",===": ["table", /* @__PURE__ */ new Set()],
5235
+ ":===": ["table", /* @__PURE__ */ new Set()],
5236
+ "!===": ["table", /* @__PURE__ */ new Set()],
5237
+ "~~~~": ["open", new Set(["abstract", "partintro"])],
5238
+ "////": ["comment", /* @__PURE__ */ new Set()],
5239
+ "```": ["fenced_code", /* @__PURE__ */ new Set()]
5240
+ };
5241
+ const DELIMITED_BLOCK_HEADS = new Set(Object.keys(DELIMITED_BLOCKS).map((delimiter) => delimiter.slice(0, 2)));
5242
+ const DELIMITED_BLOCK_TAILS = Object.fromEntries(Object.keys(DELIMITED_BLOCKS).filter((delimiter) => delimiter.length === 4).map((delimiter) => [delimiter, delimiter.slice(-1)]));
5243
+ function officialDelimiter(line) {
5244
+ let candidate = line;
5245
+ let lineLength = candidate.length;
5246
+ if (lineLength < 2 || !DELIMITED_BLOCK_HEADS.has(candidate.slice(0, 2))) return;
5247
+ let tip;
5248
+ let tipLength;
5249
+ if (lineLength === 2) {
5250
+ tip = candidate;
5251
+ tipLength = 2;
5252
+ } else {
5253
+ tipLength = lineLength < 5 ? lineLength : 4;
5254
+ tip = candidate.slice(0, tipLength);
5255
+ if (tip.startsWith("`")) {
5256
+ if (tipLength === 4) {
5257
+ if (tip === "````") return;
5258
+ tip = tip.slice(0, 3);
5259
+ if (tip !== "```") return;
5260
+ candidate = tip;
5261
+ lineLength = 3;
5262
+ tipLength = 3;
5263
+ } else if (tip !== "```") return;
5264
+ } else if (tipLength === 3) return;
5265
+ }
5266
+ const entry = DELIMITED_BLOCKS[tip];
5267
+ if (!entry) return;
5268
+ const tail = DELIMITED_BLOCK_TAILS[tip];
5269
+ if (!(lineLength === tipLength || tail !== void 0 && isUniform(candidate.slice(1), tail))) return;
5270
+ const [context, masq] = entry;
5271
+ return {
5272
+ context,
5273
+ masq,
5274
+ terminator: candidate
5275
+ };
5276
+ }
5277
+ function delimiterOpens(delimiter, context, style) {
5278
+ if (context === void 0) return false;
5279
+ if (delimiter.context === "fenced_code") return context === "listing";
5280
+ return delimiter.context === context || delimiter.masq.has(context) || style !== null && style !== void 0 && delimiter.masq.has(style);
5281
+ }
5282
+ function isUniform(text, character) {
5283
+ return [...text].every((each) => each === character);
5284
+ }
5285
+ //#endregion
5115
5286
  //#region src/source-interval-resolver.ts
5116
5287
  function resolveSourceInterval(surface, lineTable) {
5117
5288
  if (surface.sourceLine === void 0) return;
@@ -5119,11 +5290,14 @@ function resolveSourceInterval(surface, lineTable) {
5119
5290
  const metadata = metadataForSurface(surface, lineTable, blockStartLine);
5120
5291
  const startLine = metadata.at(0)?.line ?? blockStartLine;
5121
5292
  const diagnostics = [];
5122
- const delimited = resolveContextDelimitedSpan(lineTable, blockStartLine, surface.context);
5123
- const sourceContentSpan = sourceContentSpanForContext(surface, blockStartLine);
5293
+ const delimitedContext = isDelimitedContext(surface.context);
5294
+ const synthesized = borrowsChildSourceLocation(surface.block);
5295
+ const delimited = delimitedContext ? resolveDelimitedBlockSpan(lineTable, blockStartLine, surface) : void 0;
5296
+ const undelimited = delimitedContext && delimited === void 0 && !synthesized ? resolveUndelimitedBlockSpan(lineTable, blockStartLine, surface) : void 0;
5297
+ const sourceContentSpan = sourceContentSpanForContext(surface, lineTable, blockStartLine);
5124
5298
  const nextSiblingStart = nextSiblingStartLine(surface, lineTable);
5125
5299
  const childEndLine = surface.context === "dlist" ? lastChildEndLine(surface, lineTable) : void 0;
5126
- const endLine = delimited?.span.endLine ?? sourceContentSpan?.endLine ?? childEndLine ?? (nextSiblingStart !== void 0 ? nextSiblingStart - 1 : void 0) ?? parentEndLine(surface, lineTable) ?? lineTable.lines.length;
5300
+ const endLine = delimited?.span.endLine ?? undelimited?.endLine ?? sourceContentSpan?.endLine ?? childEndLine ?? (nextSiblingStart !== void 0 ? nextSiblingStart - 1 : void 0) ?? parentEndLine(surface, lineTable) ?? lineTable.lines.length;
5127
5301
  const span = normalizeLineSpan({
5128
5302
  startLine,
5129
5303
  endLine: Math.max(startLine, endLine)
@@ -5132,13 +5306,13 @@ function resolveSourceInterval(surface, lineTable) {
5132
5306
  startLine,
5133
5307
  endLine: blockStartLine - 1
5134
5308
  } : void 0;
5135
- if (delimited === void 0 && isDelimitedContext(surface.context) && blockStartLine <= lineTable.lines.length) diagnostics.push({
5309
+ if (delimitedContext && delimited === void 0 && undelimited === void 0 && !synthesized && blockStartLine <= lineTable.lines.length) diagnostics.push({
5136
5310
  level: "warning",
5137
5311
  code: "source-interval.delimiter-not-found",
5138
5312
  message: `Could not locate ${surface.context} block delimiter from official source line ${blockStartLine}.`,
5139
5313
  source: spanForLineText(lineTable, blockStartLine, 1)
5140
5314
  });
5141
- const contentSpan = delimited?.contentSpan ?? sourceContentSpan ?? contentSpanForContext(surface, span);
5315
+ const contentSpan = delimited?.contentSpan ?? undelimited ?? sourceContentSpan ?? contentSpanForContext(surface, span);
5142
5316
  const sourceSpan = sourceSpanFromLineSpan(lineTable, span);
5143
5317
  const titleSpan = titleSpanForSurface(surface, lineTable);
5144
5318
  return {
@@ -5160,6 +5334,7 @@ function nextSiblingStartLine(surface, lineTable) {
5160
5334
  }
5161
5335
  function metadataForSurface(surface, lineTable, blockStartLine) {
5162
5336
  if (surface.context === "list_item") return [];
5337
+ if (takesParentSourceLocation(surface.block)) return [];
5163
5338
  return collectPrecedingMetadata(lineTable, blockStartLine);
5164
5339
  }
5165
5340
  function lastChildEndLine(surface, lineTable) {
@@ -5175,25 +5350,10 @@ function parentEndLine(surface, lineTable) {
5175
5350
  if (!parent) return lineTable.lines.length;
5176
5351
  return resolveSourceInterval(parent, lineTable)?.span.endLine;
5177
5352
  }
5178
- function resolveContextDelimitedSpan(lineTable, startLine, context) {
5179
- switch (context) {
5180
- case "example": return resolveDelimitedBlockSpan(lineTable, startLine, ["===="]);
5181
- case "listing": return resolveDelimitedBlockSpan(lineTable, startLine, ["----", "```"]);
5182
- case "literal": return resolveDelimitedBlockSpan(lineTable, startLine, ["...."]);
5183
- case "pass": return resolveDelimitedBlockSpan(lineTable, startLine, ["++++"]);
5184
- case "open": return resolveDelimitedBlockSpan(lineTable, startLine, ["--"]);
5185
- case "quote":
5186
- case "verse": return resolveDelimitedBlockSpan(lineTable, startLine, ["____"]);
5187
- case "sidebar": return resolveDelimitedBlockSpan(lineTable, startLine, ["****"]);
5188
- case "stem": return resolveDelimitedBlockSpan(lineTable, startLine, ["++++"]);
5189
- case "table": return resolveDelimitedBlockSpan(lineTable, startLine, ["|==="]);
5190
- default: return;
5191
- }
5192
- }
5193
- function resolveDelimitedBlockSpan(lineTable, startLine, allowedDelimiters) {
5194
- const opening = lineText(lineTable, startLine).trim();
5195
- if (!allowedDelimiters.includes(opening)) return;
5196
- for (let line = startLine + 1; line <= lineTable.lines.length; line += 1) if (lineText(lineTable, line).trim() === opening) return {
5353
+ function resolveDelimitedBlockSpan(lineTable, startLine, surface) {
5354
+ const delimiter = officialDelimiter(lineText(lineTable, startLine).trimEnd());
5355
+ if (!delimiter || !delimiterOpens(delimiter, surface.context, surface.block.getStyle?.())) return;
5356
+ for (let line = startLine + 1; line <= lineTable.lines.length; line += 1) if (lineText(lineTable, line).trimEnd() === delimiter.terminator) return {
5197
5357
  span: {
5198
5358
  startLine,
5199
5359
  endLine: line
@@ -5204,6 +5364,42 @@ function resolveDelimitedBlockSpan(lineTable, startLine, allowedDelimiters) {
5204
5364
  }
5205
5365
  };
5206
5366
  }
5367
+ function resolveUndelimitedBlockSpan(lineTable, startLine, surface) {
5368
+ const firstLine = firstSourceLine(surface);
5369
+ if (firstLine === void 0 || withoutWhitespace(lineText(lineTable, startLine)) !== withoutWhitespace(firstLine)) return;
5370
+ if (surface.children.length > 0) {
5371
+ const childEnds = surface.children.map((child) => resolveSourceInterval(child, lineTable)?.span.endLine).filter((line) => line !== void 0);
5372
+ return {
5373
+ startLine,
5374
+ endLine: Math.max(startLine, ...childEnds)
5375
+ };
5376
+ }
5377
+ return {
5378
+ startLine,
5379
+ endLine: lastSourceLine(lineTable, startLine, surface.block)
5380
+ };
5381
+ }
5382
+ function withoutWhitespace(text) {
5383
+ return text.replace(/\s+/gu, "");
5384
+ }
5385
+ function lastSourceLine(lineTable, startLine, block) {
5386
+ const count = Math.max(1, block.getSourceLines?.().length ?? block.getSource?.().split(/\r?\n/u).length ?? 1);
5387
+ const skipsLineComments = block.getContentModel?.() !== "verbatim";
5388
+ let counted = 0;
5389
+ for (let line = startLine; line <= lineTable.lines.length; line += 1) {
5390
+ const text = lineText(lineTable, line);
5391
+ if (skipsLineComments && text.startsWith("//") && !text.startsWith("///")) continue;
5392
+ counted += 1;
5393
+ if (counted === count) return line;
5394
+ }
5395
+ return Math.max(startLine, lineTable.lines.length);
5396
+ }
5397
+ function firstSourceLine(surface) {
5398
+ const own = surface.block.getSourceLines?.()[0];
5399
+ if (own !== void 0) return own;
5400
+ const firstChild = surface.children[0];
5401
+ return firstChild ? firstSourceLine(firstChild) : void 0;
5402
+ }
5207
5403
  function contentSpanForContext(surface, span) {
5208
5404
  if (surface.context === "paragraph") return {
5209
5405
  startLine: surface.sourceLine ?? span.startLine,
@@ -5217,13 +5413,12 @@ function contentSpanForContext(surface, span) {
5217
5413
  };
5218
5414
  }
5219
5415
  }
5220
- function sourceContentSpanForContext(surface, startLine) {
5416
+ function sourceContentSpanForContext(surface, lineTable, startLine) {
5221
5417
  if (surface.context !== "paragraph" && surface.context !== "admonition") return;
5222
- const source = surface.block.getSource?.();
5223
- if (source === void 0) return;
5418
+ if (surface.block.getSourceLines === void 0 && surface.block.getSource === void 0) return;
5224
5419
  return {
5225
5420
  startLine,
5226
- endLine: startLine + Math.max(1, source.split(/\r?\n/u).length) - 1
5421
+ endLine: lastSourceLine(lineTable, startLine, surface.block)
5227
5422
  };
5228
5423
  }
5229
5424
  function titleSpanForSurface(surface, lineTable) {
@@ -5266,13 +5461,12 @@ function projectSourceSurfaces(options) {
5266
5461
  const blockSurfaces = walkOfficialBlocks(options.officialDocument);
5267
5462
  const intervalByBlock = /* @__PURE__ */ new WeakMap();
5268
5463
  const projectableBlocks = /* @__PURE__ */ new WeakSet();
5269
- const containerFallbackBlocks = /* @__PURE__ */ new WeakSet();
5270
5464
  const toolDiagnostics = [];
5271
5465
  const mainSourcePath = options.sourcePath ? options.sourceIdentity.normalize(options.sourceIdentity.resolve(options.sourcePath)) : void 0;
5272
5466
  const sourceAwareDocument = sourceAwareDocumentForLineTable(options.lineTable);
5273
5467
  const logicalSource = logicalSourceForLineTable(options.lineTable);
5274
5468
  for (const surface of blockSurfaces) {
5275
- const policy = officialBlockPolicy(surface.context);
5469
+ const policy = officialBlockPolicy(surface.block);
5276
5470
  if (isExternalSourceSurface(surface, mainSourcePath, options.sourceIdentity)) {
5277
5471
  if (policy === "diagnostic") toolDiagnostics.push(unknownContextDiagnostic(surface));
5278
5472
  toolDiagnostics.push({
@@ -5289,7 +5483,6 @@ function projectSourceSurfaces(options) {
5289
5483
  code: "source-location.missing",
5290
5484
  message: `Official block source location is missing for context '${surface.context ?? "undefined"}' node '${surface.nodeName ?? "undefined"}'.`
5291
5485
  });
5292
- if (canUseContainerFallbackSurface(surface, policy)) containerFallbackBlocks.add(surface.block);
5293
5486
  continue;
5294
5487
  }
5295
5488
  const interval = resolveSourceInterval(surface, options.lineTable);
@@ -5333,7 +5526,6 @@ function projectSourceSurfaces(options) {
5333
5526
  blockSurfaces,
5334
5527
  intervalByBlock,
5335
5528
  projectableBlocks,
5336
- containerFallbackBlocks,
5337
5529
  sectionByBlock,
5338
5530
  sections,
5339
5531
  xrefOccurrences,
@@ -5366,9 +5558,6 @@ function unknownContextDiagnostic(surface, interval) {
5366
5558
  source: interval?.sourceSpan
5367
5559
  });
5368
5560
  }
5369
- function canUseContainerFallbackSurface(surface, policy) {
5370
- return surface.context === "paragraph" && policy === "scan" && !hasDiagnosticPolicyAncestor(surface);
5371
- }
5372
5561
  function buildSectionSurfaces(blockSurfaces, intervalByBlock, lineTable, toolDiagnostics, logicalSource, sourceAwareDocument) {
5373
5562
  const sections = [];
5374
5563
  const sectionByBlock = /* @__PURE__ */ new WeakMap();
@@ -5486,7 +5675,7 @@ function sourceAwareOriginForLine(document, logicalLine) {
5486
5675
  function hasDiagnosticPolicyAncestor(surface) {
5487
5676
  let current = surface.parent;
5488
5677
  while (current) {
5489
- if (officialBlockPolicy(current.context) === "diagnostic") return true;
5678
+ if (officialBlockPolicy(current.block) === "diagnostic") return true;
5490
5679
  current = current.parent;
5491
5680
  }
5492
5681
  return false;
@@ -5503,6 +5692,7 @@ function mapSectionScope(sections, totalLines) {
5503
5692
  //#endregion
5504
5693
  //#region src/parser-core.ts
5505
5694
  async function parseAsciidoctorDocument(input) {
5695
+ completeSynthesizedSourceLocations(input.officialDocument);
5506
5696
  const sourceSurfaces = projectSourceSurfaces({
5507
5697
  officialDocument: input.officialDocument,
5508
5698
  lineTable: input.lineTable,
@@ -5519,7 +5709,6 @@ async function parseAsciidoctorDocument(input) {
5519
5709
  headingInlineMetadataOccurrences: sourceSurfaces.headingInlineMetadataOccurrences,
5520
5710
  intervalByBlock: sourceSurfaces.intervalByBlock,
5521
5711
  projectableBlocks: sourceSurfaces.projectableBlocks,
5522
- containerFallbackBlocks: sourceSurfaces.containerFallbackBlocks,
5523
5712
  sectionByBlock: sourceSurfaces.sectionByBlock
5524
5713
  });
5525
5714
  await resolveOfficialBindings(officialProjection.bindingRequests, input.officialDocument, input.adapter);
@@ -5587,7 +5776,8 @@ async function parseAbundantTree(options) {
5587
5776
  const sourceAwareDocument = await preprocessBookEntryWithOfficialReader({
5588
5777
  adapter,
5589
5778
  sourcePath,
5590
- documentRoot: options.documentRoot
5779
+ documentRoot: options.documentRoot,
5780
+ includeResolvers: options.includeResolvers
5591
5781
  });
5592
5782
  const constructionDiagnostic = sourceAwareDocument.diagnostics.find((diagnostic) => diagnostic.level === "error");
5593
5783
  if (constructionDiagnostic) throw constructionError(constructionDiagnostic.code, constructionDiagnostic.message, constructionDiagnostic.source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asciidoc-abundant-tree",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Recover a source-aware, analysis-ready tree from AsciiDoc files and explicit book-entry include graphs.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {