@asciidoctor/core 4.0.0-alpha.1 → 4.0.0-alpha.3

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.
@@ -5,7 +5,7 @@ const node_fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
 
7
7
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
8
- var version = "4.0.0-alpha.1";
8
+ var version = "4.0.0-alpha.3";
9
9
  const packageJson = {
10
10
  version: version};
11
11
 
@@ -1514,6 +1514,65 @@ function resolveSeverity(severity) {
1514
1514
  return severity ?? Severity.UNKNOWN
1515
1515
  }
1516
1516
 
1517
+ // ── Per-execution logger context ─────────────────────────────────────────────
1518
+
1519
+ // Holds an AsyncLocalStorage instance once it is lazily initialised.
1520
+ // Allows per-execution logger isolation without mutating the global singleton,
1521
+ // making concurrent test execution (e.g. Deno's node:test) safe.
1522
+ let _loggerStore = null;
1523
+
1524
+ // Promise singleton — ensures AsyncLocalStorage is initialised at most once.
1525
+ let _loggerStorePromise = null;
1526
+
1527
+ /**
1528
+ * Lazily initialise an AsyncLocalStorage for per-execution logger context.
1529
+ * Falls back to null in environments that do not support node:async_hooks (e.g. browsers).
1530
+ * @returns {Promise<import('node:async_hooks').AsyncLocalStorage|null>}
1531
+ */
1532
+ async function _ensureLoggerStore() {
1533
+ if (_loggerStorePromise === null) {
1534
+ _loggerStorePromise = import('node:async_hooks')
1535
+ .then(({ AsyncLocalStorage }) => {
1536
+ const store = new AsyncLocalStorage();
1537
+ _loggerStore = store;
1538
+ return store
1539
+ })
1540
+ .catch(() => null);
1541
+ }
1542
+ return _loggerStorePromise
1543
+ }
1544
+
1545
+ /** @internal — returns the logger bound to the current async context, or null */
1546
+ function getContextLogger() {
1547
+ return _loggerStore?.getStore() ?? null
1548
+ }
1549
+
1550
+ /**
1551
+ * Run fn() within an async-local logger context so that all log calls via
1552
+ * `this.logger` (from applyLogging) automatically route to the provided logger
1553
+ * for the duration of the async execution chain.
1554
+ *
1555
+ * Falls back to global mutation in environments without node:async_hooks (e.g. browsers).
1556
+ *
1557
+ * @param {Logger|MemoryLogger|NullLogger} logger - The logger to activate.
1558
+ * @param {() => any} fn - The function to execute within the logger context.
1559
+ * @returns {Promise<any>}
1560
+ */
1561
+ async function withLogger(logger, fn) {
1562
+ const store = await _ensureLoggerStore();
1563
+ if (store) {
1564
+ return store.run(logger, fn)
1565
+ }
1566
+ // Fallback for environments without node:async_hooks (browsers).
1567
+ const prev = LoggerManager.logger;
1568
+ if (logger !== prev) LoggerManager.logger = logger;
1569
+ try {
1570
+ return await fn()
1571
+ } finally {
1572
+ if (logger !== prev) LoggerManager.logger = prev;
1573
+ }
1574
+ }
1575
+
1517
1576
  // ── Logger ────────────────────────────────────────────────────────────────────
1518
1577
 
1519
1578
  /** Standard logger that writes formatted messages to stderr or a custom pipe. */
@@ -1822,8 +1881,9 @@ class MemoryLogger {
1822
1881
  // ── NullLogger ────────────────────────────────────────────────────────────────
1823
1882
 
1824
1883
  /** Logger that discards all messages but still tracks the maximum severity. */
1825
- class NullLogger {
1884
+ class NullLogger extends Logger {
1826
1885
  constructor() {
1886
+ super();
1827
1887
  this.level = Severity.UNKNOWN;
1828
1888
  this._maxSeverity = null;
1829
1889
  }
@@ -1968,12 +2028,12 @@ const LoggerManager = (() => {
1968
2028
  function applyLogging(proto) {
1969
2029
  Object.defineProperty(proto, 'logger', {
1970
2030
  get() {
1971
- return LoggerManager.logger
2031
+ return _loggerStore?.getStore() ?? LoggerManager.logger
1972
2032
  },
1973
2033
  configurable: true,
1974
2034
  });
1975
2035
 
1976
- proto.getLogger = () => LoggerManager.logger;
2036
+ proto.getLogger = () => _loggerStore?.getStore() ?? LoggerManager.logger;
1977
2037
 
1978
2038
  proto.messageWithContext = (text, context = {}) =>
1979
2039
  Logger.AutoFormattingMessage.attach({ text, ...context });
@@ -2020,7 +2080,16 @@ const rstrip = (line) => line.replace(/[ \t\r\n\f\v]+$/, '');
2020
2080
  */
2021
2081
  function prepareSourceArray(data, trimEnd = true) {
2022
2082
  if (!data.length) return []
2023
- if (data[0].startsWith(BOM)) data[0] = data[0].slice(1);
2083
+ if (data[0].startsWith(BOM)) {
2084
+ data[0] = data[0].slice(1);
2085
+ } else if (
2086
+ data[0].charCodeAt(0) === 0xef &&
2087
+ data[0].charCodeAt(1) === 0xbb &&
2088
+ data[0].charCodeAt(2) === 0xbf
2089
+ ) {
2090
+ // Strip UTF-8 BOM encoded as three raw characters ( / \xEF\xBB\xBF) if not already decoded to U+FEFF
2091
+ data[0] = data[0].slice(3);
2092
+ }
2024
2093
  // Strip trailing \r to normalize Windows CRLF line endings (lines were split on \n, leaving \r).
2025
2094
  return trimEnd
2026
2095
  ? data.map(rstrip)
@@ -2040,7 +2109,16 @@ function prepareSourceArray(data, trimEnd = true) {
2040
2109
  */
2041
2110
  function prepareSourceString(data, trimEnd = true) {
2042
2111
  if (!data) return []
2043
- if (data.startsWith(BOM)) data = data.slice(1);
2112
+ if (data.startsWith(BOM)) {
2113
+ data = data.slice(1);
2114
+ } else if (
2115
+ data.charCodeAt(0) === 0xef &&
2116
+ data.charCodeAt(1) === 0xbb &&
2117
+ data.charCodeAt(2) === 0xbf
2118
+ ) {
2119
+ // Strip UTF-8 BOM encoded as three raw characters ( / \xEF\xBB\xBF) if not already decoded to U+FEFF
2120
+ data = data.slice(3);
2121
+ }
2044
2122
  // Normalize Windows CRLF to LF so that split('\n') does not leave trailing \r on each line.
2045
2123
  if (data.includes('\r'))
2046
2124
  data = data.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
@@ -3750,7 +3828,7 @@ class AbstractBlock extends AbstractNode {
3750
3828
  * - `'reject'` → skip the node and its children
3751
3829
  * - `'stop'` → include the node (if it matched) and stop the entire traversal
3752
3830
  *
3753
- * @param {Object} [selector={}] - Selector criteria object.
3831
+ * @param {Object|Function} [selector={}] - Selector criteria object, or a filter callback when called as `findBy(callback)`.
3754
3832
  * @param {Function|null} [filter=null] - Per-node filter callback.
3755
3833
  * @returns {AbstractBlock[]} array of matching block-level nodes.
3756
3834
  *
@@ -3765,6 +3843,9 @@ class AbstractBlock extends AbstractNode {
3765
3843
  *
3766
3844
  * @example <caption>All image blocks including those inside AsciiDoc table cells</caption>
3767
3845
  * const images = doc.findBy({ context: 'image', traverseDocuments: true })
3846
+ *
3847
+ * @example <caption>Filter-only shorthand (no selector)</caption>
3848
+ * const verbatim = doc.findBy((b) => b.contentModel === ContentModel.VERBATIM)
3768
3849
  */
3769
3850
  findBy(selector = {}, filter = null) {
3770
3851
  const result = [];
@@ -3773,8 +3854,13 @@ class AbstractBlock extends AbstractNode {
3773
3854
  selector && typeof selector === 'object' && !Array.isArray(selector)
3774
3855
  ? selector
3775
3856
  : {};
3776
- // Normalise: filter must be a function; ignore string/non-function values (mirrors core API).
3777
- const normFilter = typeof filter === 'function' ? filter : null;
3857
+ // Normalise: support findBy(callback) shorthand selector is the filter when it's a function.
3858
+ const normFilter =
3859
+ typeof filter === 'function'
3860
+ ? filter
3861
+ : typeof selector === 'function'
3862
+ ? selector
3863
+ : null;
3778
3864
  try {
3779
3865
  this.#findByInternal(normSelector, result, normFilter);
3780
3866
  } catch (e) {
@@ -5964,7 +6050,7 @@ const SyntaxHighlighter = new DefaultFactory();
5964
6050
  //
5965
6051
  // This logic is specific to Asciidoctor.js and has no equivalent in the upstream Ruby asciidoctor
5966
6052
  // implementation. It handles the case where the document is loaded in a browser environment
5967
- // (XMLHttpRequest / Fetch IO module) where paths can be file:// or http(s):// URIs.
6053
+ // where paths can be file:// or http(s):// URIs.
5968
6054
  //
5969
6055
  // The key behavioural differences from the standard file-system resolver:
5970
6056
  // - Relative targets are resolved by string concatenation against a URI context dir,
@@ -6013,7 +6099,7 @@ function _linkReplacement(reader, target, attrlist) {
6013
6099
  * 1. target starts with file:// → inc_path = relpath = target
6014
6100
  * 2. target is a URI → must descend from baseDir or allow-uri-read; else → link
6015
6101
  * 3. target is an absolute OS path → prepend file:// (or file:///)
6016
- * 4. baseDir == '.' → inc_path = relpath = target (resolved by XMLHttpRequest/fetch)
6102
+ * 4. baseDir == '.' → inc_path = relpath = target (resolved by fetch)
6017
6103
  * 5. baseDir starts with file:// OR baseDir is not a URI → inc_path = baseDir/target; relpath = target
6018
6104
  * 6. baseDir is an absolute URL → inc_path = baseDir/target; relpath = target
6019
6105
  *
@@ -6735,7 +6821,7 @@ class Reader {
6735
6821
  return this.source()
6736
6822
  }
6737
6823
  getLogger() {
6738
- return LoggerManager.logger
6824
+ return this._document?.logger ?? LoggerManager.logger
6739
6825
  }
6740
6826
  createLogMessage(text, context = {}) {
6741
6827
  return Logger.AutoFormattingMessage.attach({ text, ...context })
@@ -9560,6 +9646,22 @@ class AttributeList {
9560
9646
  }
9561
9647
  }
9562
9648
 
9649
+ // Symbol key used to store attribute entries on a block-attributes object without
9650
+ // polluting the public attributes (invisible to Object.keys / for-in / JSON.stringify).
9651
+ // Spread ({ ...attrs }) copies Symbol-keyed properties, so the entry survives the
9652
+ // shallow clone made in AbstractNode's constructor.
9653
+ const ATTR_ENTRIES_KEY = Symbol('attribute_entries');
9654
+
9655
+ /**
9656
+ * Return the attribute entries stored for the given block attributes object,
9657
+ * or undefined if none have been saved.
9658
+ * @param {Object} blockAttributes
9659
+ * @returns {AttributeEntry[]|undefined}
9660
+ */
9661
+ function getAttributeEntries(blockAttributes) {
9662
+ return blockAttributes[ATTR_ENTRIES_KEY]
9663
+ }
9664
+
9563
9665
  class AttributeEntry {
9564
9666
  constructor(name, value, negate = null) {
9565
9667
  this.name = name;
@@ -9568,7 +9670,7 @@ class AttributeEntry {
9568
9670
  }
9569
9671
 
9570
9672
  saveTo(blockAttributes) {
9571
- (blockAttributes.attribute_entries ??= []).push(this);
9673
+ (blockAttributes[ATTR_ENTRIES_KEY] ??= []).push(this);
9572
9674
  return this
9573
9675
  }
9574
9676
  }
@@ -11043,7 +11145,7 @@ class Parser {
11043
11145
  const Rdr = Reader;
11044
11146
  const result = await extension.processMethod(
11045
11147
  parent,
11046
- blockReader ?? new Rdr(lines),
11148
+ blockReader ?? new Rdr(lines, null, { document: parent.document }),
11047
11149
  { ...attributes }
11048
11150
  );
11049
11151
  if (!result || result === parent) return null
@@ -13165,6 +13267,94 @@ function _uniform(str, chr, len) {
13165
13267
  // references; they will be resolved when those modules implement the methods.
13166
13268
 
13167
13269
 
13270
+ // Type-only imports for JSDoc
13271
+ /**
13272
+ * @typedef {import('./document.js').Document} Document
13273
+ * @typedef {import('./abstract_block.js').AbstractBlock} AbstractBlock
13274
+ */
13275
+
13276
+ // ── DSL interface types ───────────────────────────────────────────────────────
13277
+
13278
+ /**
13279
+ * DSL interface for configuring a {@link Processor} instance.
13280
+ * Applied to a processor instance via `Object.assign(instance, DslMixin)`.
13281
+ *
13282
+ * The `process` property behaves as a setter when called with a single Function
13283
+ * argument (stores the process block), or as a passthrough caller otherwise.
13284
+ *
13285
+ * @typedef {object} ProcessorDslInterface
13286
+ * @property {(key: string, value: unknown) => void} option - Set a config option.
13287
+ * @property {(fn: (...args: unknown[]) => unknown) => void} process - Register the process function.
13288
+ * @property {() => boolean} processBlockGiven - Returns true if a process function has been registered.
13289
+ */
13290
+
13291
+ /**
13292
+ * DSL interface for document processors (Preprocessor, TreeProcessor, Postprocessor, DocinfoProcessor).
13293
+ *
13294
+ * @typedef {ProcessorDslInterface & { prefer(): void; prepend(): void }} DocumentProcessorDslInterface
13295
+ */
13296
+
13297
+ /**
13298
+ * DSL interface for syntax processors (BlockProcessor, BlockMacroProcessor, InlineMacroProcessor).
13299
+ *
13300
+ * @typedef {ProcessorDslInterface & {
13301
+ * named(value: string): void;
13302
+ * contentModel(value: string): void;
13303
+ * parseContentAs(value: string): void;
13304
+ * positionalAttributes(...value: string[]): void;
13305
+ * namePositionalAttributes(...value: string[]): void;
13306
+ * positionalAttrs(...value: string[]): void;
13307
+ * defaultAttributes(value: Record<string, string>): void;
13308
+ * defaultAttrs(value: Record<string, string>): void;
13309
+ * resolveAttributes(...args: unknown[]): void;
13310
+ * resolvesAttributes(...args: unknown[]): void;
13311
+ * }} SyntaxProcessorDslInterface
13312
+ */
13313
+
13314
+ /**
13315
+ * DSL interface for include processors.
13316
+ *
13317
+ * @typedef {DocumentProcessorDslInterface & {
13318
+ * handles(fn: (doc: Document, target: string) => boolean): void;
13319
+ * }} IncludeProcessorDslInterface
13320
+ */
13321
+
13322
+ /**
13323
+ * DSL interface for docinfo processors.
13324
+ *
13325
+ * @typedef {DocumentProcessorDslInterface & {
13326
+ * atLocation(value: string): void;
13327
+ * }} DocinfoProcessorDslInterface
13328
+ */
13329
+
13330
+ /**
13331
+ * DSL interface for block processors.
13332
+ *
13333
+ * @typedef {SyntaxProcessorDslInterface & {
13334
+ * contexts(...value: (string | string[])[]): void;
13335
+ * onContexts(...value: (string | string[])[]): void;
13336
+ * onContext(...value: (string | string[])[]): void;
13337
+ * bindTo(...value: (string | string[])[]): void;
13338
+ * }} BlockProcessorDslInterface
13339
+ */
13340
+
13341
+ /**
13342
+ * DSL interface for macro processors (block and inline macros).
13343
+ *
13344
+ * @typedef {SyntaxProcessorDslInterface} MacroProcessorDslInterface
13345
+ */
13346
+
13347
+ /**
13348
+ * DSL interface for inline macro processors.
13349
+ *
13350
+ * @typedef {MacroProcessorDslInterface & {
13351
+ * format(value: string): void;
13352
+ * matchFormat(value: string): void;
13353
+ * usingFormat(value: string): void;
13354
+ * match(value: RegExp): void;
13355
+ * }} InlineMacroProcessorDslInterface
13356
+ */
13357
+
13168
13358
  // ── DSL Mixins ────────────────────────────────────────────────────────────────
13169
13359
 
13170
13360
  /**
@@ -13760,7 +13950,12 @@ class Processor {
13760
13950
  * Extensions.register(function () { this.preprocessor(CommentStripPreprocessor) })
13761
13951
  */
13762
13952
  class Preprocessor extends Processor {
13763
- process(_document, _reader) {
13953
+ /**
13954
+ * @param {Document} document - The document being parsed.
13955
+ * @param {Reader} reader - The reader positioned at the beginning of the source.
13956
+ * @returns {Reader|undefined} The same or a substitute Reader, or undefined to use the original.
13957
+ */
13958
+ process(document, reader) {
13764
13959
  throw new Error(
13765
13960
  `${this.constructor.name} must implement the process method`
13766
13961
  )
@@ -13785,7 +13980,11 @@ Preprocessor.DSL = DocumentProcessorDsl;
13785
13980
  * Extensions.register(function () { this.treeProcessor(ShoutTreeProcessor) })
13786
13981
  */
13787
13982
  class TreeProcessor extends Processor {
13788
- process(_document) {
13983
+ /**
13984
+ * @param {Document} document - The parsed document.
13985
+ * @returns {void}
13986
+ */
13987
+ process(document) {
13789
13988
  throw new Error(
13790
13989
  `${this.constructor.name} must implement the process method`
13791
13990
  )
@@ -13811,7 +14010,12 @@ TreeProcessor.DSL = DocumentProcessorDsl;
13811
14010
  * Extensions.register(function () { this.postprocessor(FooterPostprocessor) })
13812
14011
  */
13813
14012
  class Postprocessor extends Processor {
13814
- process(_document, _output) {
14013
+ /**
14014
+ * @param {Document} document - The converted document.
14015
+ * @param {string} output - The converted output string.
14016
+ * @returns {string} The (possibly modified) output string.
14017
+ */
14018
+ process(document, output) {
13815
14019
  throw new Error(
13816
14020
  `${this.constructor.name} must implement the process method`
13817
14021
  )
@@ -13825,13 +14029,25 @@ Postprocessor.DSL = DocumentProcessorDsl;
13825
14029
  * Implementations must extend IncludeProcessor.
13826
14030
  */
13827
14031
  class IncludeProcessor extends Processor {
13828
- process(_document, _reader, _target, _attributes) {
14032
+ /**
14033
+ * @param {Document} document - The document being parsed.
14034
+ * @param {Reader} reader - The reader for the including document.
14035
+ * @param {string} target - The target of the include directive.
14036
+ * @param {Record<string, string>} attributes - The parsed include attributes.
14037
+ * @returns {void}
14038
+ */
14039
+ process(document, reader, target, attributes) {
13829
14040
  throw new Error(
13830
14041
  `${this.constructor.name} must implement the process method`
13831
14042
  )
13832
14043
  }
13833
14044
 
13834
- handles(_doc, _target) {
14045
+ /**
14046
+ * @param {Document} doc - The document being parsed.
14047
+ * @param {string} target - The target of the include directive.
14048
+ * @returns {boolean} true if this processor handles the given target.
14049
+ */
14050
+ handles(doc, target) {
13835
14051
  return true
13836
14052
  }
13837
14053
  }
@@ -13849,7 +14065,11 @@ class DocinfoProcessor extends Processor {
13849
14065
  this.config.location ??= 'head';
13850
14066
  }
13851
14067
 
13852
- process(_document) {
14068
+ /**
14069
+ * @param {Document} document - The document being converted.
14070
+ * @returns {string} The docinfo content to inject into the document.
14071
+ */
14072
+ process(document) {
13853
14073
  throw new Error(
13854
14074
  `${this.constructor.name} must implement the process method`
13855
14075
  )
@@ -13905,7 +14125,13 @@ class BlockProcessor extends Processor {
13905
14125
  this.config.content_model ??= 'compound';
13906
14126
  }
13907
14127
 
13908
- process(_parent, _reader, _attributes) {
14128
+ /**
14129
+ * @param {AbstractBlock} parent - The enclosing block.
14130
+ * @param {Reader} reader - The reader positioned at the block content.
14131
+ * @param {Record<string, unknown>} attributes - The parsed block attributes.
14132
+ * @returns {Block|void} A block node, or void to let the parser handle it.
14133
+ */
14134
+ process(parent, reader, attributes) {
13909
14135
  throw new Error(
13910
14136
  `${this.constructor.name} must implement the process method`
13911
14137
  )
@@ -13923,7 +14149,13 @@ class MacroProcessor extends Processor {
13923
14149
  this.config.content_model ??= 'attributes';
13924
14150
  }
13925
14151
 
13926
- process(_parent, _target, _attributes) {
14152
+ /**
14153
+ * @param {AbstractBlock} parent - The enclosing block.
14154
+ * @param {string} target - The macro target (text between `name:` and `[`).
14155
+ * @param {Record<string, unknown>} attributes - The parsed macro attributes.
14156
+ * @returns {Block|Inline|void}
14157
+ */
14158
+ process(parent, target, attributes) {
13927
14159
  throw new Error(
13928
14160
  `${this.constructor.name} must implement the process method`
13929
14161
  )
@@ -13969,6 +14201,16 @@ class BlockMacroProcessor extends MacroProcessor {
13969
14201
  set name(value) {
13970
14202
  this._name = value;
13971
14203
  }
14204
+
14205
+ /**
14206
+ * @param {AbstractBlock} parent - The enclosing block.
14207
+ * @param {string} target - The macro target.
14208
+ * @param {Record<string, unknown>} attributes - The parsed macro attributes.
14209
+ * @returns {Block} A block node created with one of the `createBlock` helpers.
14210
+ */
14211
+ process(parent, target, attributes) {
14212
+ return super.process(parent, target, attributes)
14213
+ }
13972
14214
  }
13973
14215
  BlockMacroProcessor.DSL = MacroProcessorDsl;
13974
14216
 
@@ -14012,6 +14254,16 @@ class InlineMacroProcessor extends MacroProcessor {
14012
14254
  ))
14013
14255
  }
14014
14256
 
14257
+ /**
14258
+ * @param {AbstractBlock} parent - The enclosing block.
14259
+ * @param {string} target - The macro target.
14260
+ * @param {Record<string, unknown>} attributes - The parsed macro attributes.
14261
+ * @returns {Inline} An Inline node created with `this.createInline(...)`.
14262
+ */
14263
+ process(parent, target, attributes) {
14264
+ return super.process(parent, target, attributes)
14265
+ }
14266
+
14015
14267
  resolveRegexp(name, format) {
14016
14268
  if (!MacroNameRx.test(name)) {
14017
14269
  throw new Error(`invalid name for inline macro: ${name}`)
@@ -14375,6 +14627,15 @@ class Registry {
14375
14627
  return !!this._block_extensions
14376
14628
  }
14377
14629
 
14630
+ /**
14631
+ * Retrieve all BlockProcessor Extension proxy objects.
14632
+ *
14633
+ * @returns {ProcessorExtension[]}
14634
+ */
14635
+ blocks() {
14636
+ return this._block_extensions ? Object.values(this._block_extensions) : []
14637
+ }
14638
+
14378
14639
  /**
14379
14640
  * Check whether a BlockProcessor is registered for the given name and context.
14380
14641
  *
@@ -14421,6 +14682,17 @@ class Registry {
14421
14682
  return !!this._block_macro_extensions
14422
14683
  }
14423
14684
 
14685
+ /**
14686
+ * Retrieve all BlockMacroProcessor Extension proxy objects.
14687
+ *
14688
+ * @returns {ProcessorExtension[]}
14689
+ */
14690
+ blockMacros() {
14691
+ return this._block_macro_extensions
14692
+ ? Object.values(this._block_macro_extensions)
14693
+ : []
14694
+ }
14695
+
14424
14696
  /**
14425
14697
  * Check whether a BlockMacroProcessor is registered for the given name.
14426
14698
  *
@@ -14526,6 +14798,85 @@ class Registry {
14526
14798
  return extension
14527
14799
  }
14528
14800
 
14801
+ // ── JavaScript-style accessors ───────────────────────────────────────────────
14802
+
14803
+ /** @returns {object} the plain Object that maps names to groups for this registry. */
14804
+ getGroups() {
14805
+ return this.groups
14806
+ }
14807
+
14808
+ /** Alias for {@link preprocessors}. */
14809
+ getPreprocessors() {
14810
+ return this.preprocessors()
14811
+ }
14812
+
14813
+ /** Alias for {@link treeProcessors}. */
14814
+ getTreeProcessors() {
14815
+ return this.treeProcessors()
14816
+ }
14817
+
14818
+ /** Alias for {@link includeProcessors}. */
14819
+ getIncludeProcessors() {
14820
+ return this.includeProcessors()
14821
+ }
14822
+
14823
+ /** Alias for {@link postprocessors}. */
14824
+ getPostprocessors() {
14825
+ return this.postprocessors()
14826
+ }
14827
+
14828
+ /**
14829
+ * Alias for {@link docinfoProcessors}.
14830
+ *
14831
+ * @param {string|null} [location=null]
14832
+ */
14833
+ getDocinfoProcessors(location = null) {
14834
+ return this.docinfoProcessors(location)
14835
+ }
14836
+
14837
+ /** Alias for {@link blocks}. */
14838
+ getBlocks() {
14839
+ return this.blocks()
14840
+ }
14841
+
14842
+ /** Alias for {@link blockMacros}. */
14843
+ getBlockMacros() {
14844
+ return this.blockMacros()
14845
+ }
14846
+
14847
+ /** Alias for {@link inlineMacros}. */
14848
+ getInlineMacros() {
14849
+ return this.inlineMacros()
14850
+ }
14851
+
14852
+ /**
14853
+ * Alias for {@link registeredForInlineMacro}.
14854
+ *
14855
+ * @param {string} name
14856
+ */
14857
+ getInlineMacroFor(name) {
14858
+ return this.registeredForInlineMacro(name)
14859
+ }
14860
+
14861
+ /**
14862
+ * Alias for {@link registeredForBlock}.
14863
+ *
14864
+ * @param {string} name
14865
+ * @param {string} context
14866
+ */
14867
+ getBlockFor(name, context) {
14868
+ return this.registeredForBlock(name, context)
14869
+ }
14870
+
14871
+ /**
14872
+ * Alias for {@link registeredForBlockMacro}.
14873
+ *
14874
+ * @param {string} name
14875
+ */
14876
+ getBlockMacroFor(name) {
14877
+ return this.registeredForBlockMacro(name)
14878
+ }
14879
+
14529
14880
  // ── Private helpers ──────────────────────────────────────────────────────────
14530
14881
 
14531
14882
  /** @internal */
@@ -14751,6 +15102,15 @@ const Extensions = {
14751
15102
  return _groups
14752
15103
  },
14753
15104
 
15105
+ /**
15106
+ * Alias for {@link groups}.
15107
+ *
15108
+ * @returns {object}
15109
+ */
15110
+ getGroups() {
15111
+ return this.groups()
15112
+ },
15113
+
14754
15114
  /**
14755
15115
  * Create a new Registry, optionally pre-populated with a named block.
14756
15116
  *
@@ -15978,8 +16338,9 @@ class Document extends AbstractBlock {
15978
16338
  * @param {Object} blockAttributes
15979
16339
  */
15980
16340
  playbackAttributes(blockAttributes) {
15981
- if (!('attribute_entries' in blockAttributes)) return
15982
- for (const entry of blockAttributes.attribute_entries) {
16341
+ const entries = getAttributeEntries(blockAttributes);
16342
+ if (!entries) return
16343
+ for (const entry of entries) {
15983
16344
  if (entry.negate) {
15984
16345
  delete this.attributes[entry.name];
15985
16346
  if (entry.name === 'compat-mode') this.compatMode = false;
@@ -16708,6 +17069,12 @@ class Document extends AbstractBlock {
16708
17069
  * Handles titles (AbstractBlock), list item text, table cell text, and reftexts.
16709
17070
  */
16710
17071
  async _resolveAllTexts(block) {
17072
+ // The header section lives outside document.blocks; pre-compute its title here so
17073
+ // that doc.doctitle() returns the fully-substituted title (with replacements applied,
17074
+ // e.g. ' → &#8217;) rather than the header-subs-only fallback.
17075
+ if (block === this && this.header) {
17076
+ await this.header.precomputeTitle?.();
17077
+ }
16711
17078
  // Skip title pre-computation for blocks with an explicit empty id ([id=]).
16712
17079
  // In Ruby, apply_title_subs is lazy: it is never called during parsing for such
16713
17080
  // blocks because section.title is never accessed. An explicit empty id is
@@ -16791,7 +17158,7 @@ class Document extends AbstractBlock {
16791
17158
  * @internal
16792
17159
  */
16793
17160
  _clearPlaybackAttributes(attributes) {
16794
- delete attributes.attribute_entries;
17161
+ delete attributes[ATTR_ENTRIES_KEY];
16795
17162
  }
16796
17163
 
16797
17164
  /**
@@ -19519,17 +19886,23 @@ async function load$1(input, options = {}) {
19519
19886
  // Shallow-copy options so we don't mutate the caller's object.
19520
19887
  options = Object.assign({}, options);
19521
19888
 
19522
- const timings = options.timings ?? null;
19523
- if (timings) timings.start('read');
19524
-
19525
19889
  // ── Logger override ───────────────────────────────────────────────────────
19890
+ // When a logger option is supplied, run the conversion in an async-local
19891
+ // context so the logger is scoped to this call only — no global mutation,
19892
+ // which makes concurrent callers (e.g. parallel Deno tests) safe.
19526
19893
  if ('logger' in options) {
19527
- const logger = options.logger;
19528
- if (logger !== LoggerManager.logger) {
19529
- LoggerManager.logger = logger ?? new NullLogger();
19530
- }
19894
+ const newLogger = options.logger ?? new NullLogger();
19895
+ delete options.logger;
19896
+ return withLogger(newLogger, () => _doLoad(input, options, newLogger))
19531
19897
  }
19532
19898
 
19899
+ return _doLoad(input, options)
19900
+ }
19901
+
19902
+ async function _doLoad(input, options, explicitLogger = null) {
19903
+ const timings = options.timings ?? null;
19904
+ if (timings) timings.start('read');
19905
+
19533
19906
  // ── Attributes normalisation ──────────────────────────────────────────────
19534
19907
  let attrs = options.attributes;
19535
19908
  if (!attrs) {
@@ -19561,11 +19934,9 @@ async function load$1(input, options = {}) {
19561
19934
  attrs.docname = basename(inputPath, docfilesuffix);
19562
19935
  }
19563
19936
  source = await _readStream(input);
19564
- } else if (
19565
- typeof input === 'object' &&
19566
- input?.constructor?.name === 'Buffer'
19567
- ) {
19568
- source = input.toString('utf8');
19937
+ } else if (input instanceof Uint8Array) {
19938
+ // Covers both Node.js Buffer (a Uint8Array subclass) and browser Uint8Array shims.
19939
+ source = new TextDecoder('utf-8').decode(input);
19569
19940
  } else if (typeof input === 'string') {
19570
19941
  source = input;
19571
19942
  } else if (Array.isArray(input)) {
@@ -19628,6 +19999,19 @@ async function load$1(input, options = {}) {
19628
19999
  }
19629
20000
 
19630
20001
  if (timings) timings.record('parse');
20002
+
20003
+ // Persist the logger on the Document instance so that doc.convert()
20004
+ // (called by convert.js after the async-local context ends) still routes
20005
+ // logging through the caller-supplied logger.
20006
+ // ALS provides it in Node.js/Deno; the explicit parameter covers browser fallback.
20007
+ const contextLogger = getContextLogger() ?? explicitLogger;
20008
+ if (contextLogger) {
20009
+ Object.defineProperty(doc, 'logger', {
20010
+ get: () => contextLogger,
20011
+ configurable: true,
20012
+ });
20013
+ }
20014
+
19631
20015
  return doc
19632
20016
  }
19633
20017
 
@@ -21038,10 +21422,7 @@ ${await node.content()}
21038
21422
  img =
21039
21423
  (await this.readSvgContents(node, target)) ||
21040
21424
  `<span class="alt">${node.getAlt()}</span>`;
21041
- } else if (
21042
- node.hasOption('interactive') &&
21043
- node.document.safe >= SafeMode.SERVER
21044
- ) {
21425
+ } else if (node.hasOption('interactive')) {
21045
21426
  const fallback = node.hasAttribute('fallback')
21046
21427
  ? `<img src="${await node.imageUri(node.getAttribute('fallback'))}" alt="${this._encodeAttrValue(node.getAlt())}"${widthAttr}${heightAttr}${slash}>`
21047
21428
  : `<span class="alt">${node.getAlt()}</span>`;
@@ -21882,10 +22263,7 @@ Your browser does not support the video tag.
21882
22263
  img =
21883
22264
  (await this.readSvgContents(node, target)) ||
21884
22265
  `<span class="alt">${node.getAlt()}</span>`;
21885
- } else if (
21886
- node.hasOption('interactive') &&
21887
- node.document.safe >= SafeMode.SERVER
21888
- ) {
22266
+ } else if (node.hasOption('interactive')) {
21889
22267
  const fallback = node.hasAttribute('fallback')
21890
22268
  ? `<img src="${await node.imageUri(node.getAttribute('fallback'))}" alt="${this._encodeAttrValue(node.getAlt())}"${attrs}${this._voidSlash}>`
21891
22269
  : `<span class="alt">${node.getAlt()}</span>`;
@@ -22532,7 +22910,7 @@ class TemplateConverter extends ConverterBase {
22532
22910
 
22533
22911
  let entries;
22534
22912
  try {
22535
- entries = await node_fs.promises.readdir(templateDir);
22913
+ entries = (await node_fs.promises.readdir(templateDir)).sort();
22536
22914
  } catch {
22537
22915
  return result
22538
22916
  }