@asciidoctor/core 4.0.0-alpha.6 → 4.0.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/src/extensions.js CHANGED
@@ -75,6 +75,7 @@ import { MacroNameRx, CC_ANY } from './rx.js'
75
75
  * DSL interface for include processors.
76
76
  *
77
77
  * @typedef {DocumentProcessorDslInterface & {
78
+ * handles(fn: (target: string) => boolean): void;
78
79
  * handles(fn: (doc: Document, target: string) => boolean): void;
79
80
  * }} IncludeProcessorDslInterface
80
81
  */
@@ -125,7 +126,7 @@ import { MacroNameRx, CC_ANY } from './rx.js'
125
126
  * - Called with a single Function argument → stores it as the process block.
126
127
  * - Called with non-Function arguments → invokes the stored process block.
127
128
  *
128
- * The this context inside a stored process function is bound to the processor
129
+ * The `this` context inside a stored process function is bound to the processor
129
130
  * instance at definition time.
130
131
  */
131
132
  export const ProcessorDsl = {
@@ -299,6 +300,35 @@ export const SyntaxProcessorDsl = {
299
300
  export const IncludeProcessorDsl = {
300
301
  ...DocumentProcessorDsl,
301
302
 
303
+ /**
304
+ * @overload
305
+ * @param {(target: string) => boolean} fn - Predicate that receives only the include target.
306
+ * @returns {void}
307
+ */
308
+ /**
309
+ * @overload
310
+ * @param {(doc: Document, target: string) => boolean} fn - Predicate that receives the document and the include target.
311
+ * @returns {void}
312
+ */
313
+ /**
314
+ * @overload
315
+ * @param {Document} doc - The document being parsed.
316
+ * @param {string} target - The include target.
317
+ * @returns {boolean}
318
+ */
319
+ /**
320
+ * Register a function that decides whether this include processor handles a given target,
321
+ * or invoke the registered handler.
322
+ *
323
+ * **Setter form** — register a predicate. The callback may accept either just the target
324
+ * string (arity 1) or both the document and the target string (arity 2).
325
+ *
326
+ * **Invoker form** — call the registered predicate with `(doc, target)` and return its
327
+ * result, or return `true` if no predicate has been registered.
328
+ *
329
+ * @param {...*} args
330
+ * @returns {boolean | void}
331
+ */
302
332
  handles(...args) {
303
333
  if (args.length === 1 && typeof args[0] === 'function') {
304
334
  const fn = args[0]
@@ -1072,22 +1102,48 @@ export class ProcessorExtension extends Extension {
1072
1102
  super(kind, instance, instance.config)
1073
1103
  this.processMethod =
1074
1104
  processMethod || ((...args) => instance.process(...args))
1105
+ /** @internal */
1106
+ this._direct = false
1075
1107
  }
1076
1108
  }
1077
1109
 
1078
1110
  // ── Group ─────────────────────────────────────────────────────────────────────
1079
1111
 
1080
1112
  /**
1081
- * A Group registers one or more extensions with a Registry.
1113
+ * A Group bundles one or more extension registrations that are re-executed on
1114
+ * every document conversion, making the registry safe to reuse.
1115
+ *
1116
+ * Subclass Group, override {@link Group#activate}, and either call
1117
+ * {@link Group.register} to add it globally or pass the subclass to
1118
+ * {@link Extensions.create} / {@link Extensions.register}.
1082
1119
  *
1083
- * Subclass Group and pass the subclass to Extensions.register(), or call
1084
- * the static register() method directly.
1120
+ * @example
1121
+ * class MyGroup extends Group {
1122
+ * activate (registry) {
1123
+ * registry.preprocessor(MyPreprocessor)
1124
+ * }
1125
+ * }
1126
+ * MyGroup.register()
1085
1127
  */
1086
1128
  export class Group {
1129
+ /**
1130
+ * Register this Group class globally under the given name.
1131
+ *
1132
+ * Equivalent to calling `Extensions.register(name, this)`.
1133
+ *
1134
+ * @param {string|null} [name] - Optional name for the group.
1135
+ */
1087
1136
  static register(name = null) {
1088
1137
  Extensions.register(name, this)
1089
1138
  }
1090
1139
 
1140
+ /**
1141
+ * Called by {@link Registry#activate} on every document conversion.
1142
+ *
1143
+ * Override this method to register extensions with the provided registry.
1144
+ *
1145
+ * @param {Registry} _registry - The registry to register extensions with.
1146
+ */
1091
1147
  activate(_registry) {
1092
1148
  throw new Error(
1093
1149
  `${this.constructor.name} must implement the activate method`
@@ -1119,10 +1175,24 @@ const SYNTAX_PROCESSOR_CLASSES = {
1119
1175
  * Registry holds the extensions which have been registered and activated, has
1120
1176
  * methods for registering or defining a processor and looks up extensions
1121
1177
  * stored in the registry during parsing.
1178
+ *
1179
+ * A registry can be reused across multiple conversions. Extensions registered
1180
+ * via a group block (passed to {@link Extensions.create} or
1181
+ * {@link Extensions.register}) are re-executed on every activation. Extensions
1182
+ * registered directly on the registry instance (e.g. `registry.preprocessor(fn)`)
1183
+ * are preserved across activations.
1184
+ *
1185
+ * @example
1186
+ * const registry = Extensions.create('my-ext', function () {
1187
+ * this.preprocessor(function () { ... })
1188
+ * })
1189
+ * // registry can be passed to multiple conversions safely
1122
1190
  */
1123
1191
  export class Registry {
1124
1192
  constructor(groups = {}) {
1125
1193
  this.groups = groups
1194
+ /** @internal */
1195
+ this._activating = false
1126
1196
  this._reset()
1127
1197
  }
1128
1198
 
@@ -1139,18 +1209,26 @@ export class Registry {
1139
1209
  ...Object.values(Extensions.groups()),
1140
1210
  ...Object.values(this.groups),
1141
1211
  ]
1142
- for (const group of extGroups) {
1143
- if (typeof group === 'function') {
1144
- // Check if it is a class (constructor) with an activate prototype method.
1145
- if (group.prototype && typeof group.prototype.activate === 'function') {
1146
- new group().activate(this)
1147
- } else {
1148
- // Plain function — call in the context of this registry (like instance_exec).
1149
- group.length === 0 ? group.call(this) : group(this)
1212
+ this._activating = true
1213
+ try {
1214
+ for (const group of extGroups) {
1215
+ if (typeof group === 'function') {
1216
+ // Check if it is a class (constructor) with an activate prototype method.
1217
+ if (
1218
+ group.prototype &&
1219
+ typeof group.prototype.activate === 'function'
1220
+ ) {
1221
+ new group().activate(this)
1222
+ } else {
1223
+ // Plain function — call in the context of this registry (like instance_exec).
1224
+ group.length === 0 ? group.call(this) : group(this)
1225
+ }
1226
+ } else if (group && typeof group.activate === 'function') {
1227
+ group.activate(this)
1150
1228
  }
1151
- } else if (group && typeof group.activate === 'function') {
1152
- group.activate(this)
1153
1229
  }
1230
+ } finally {
1231
+ this._activating = false
1154
1232
  }
1155
1233
  return this
1156
1234
  }
@@ -1726,6 +1804,7 @@ export class Registry {
1726
1804
  }
1727
1805
 
1728
1806
  const extension = new ProcessorExtension(kind, processorInstance)
1807
+ extension._direct = !this._activating
1729
1808
  extension.config.position === '>>'
1730
1809
  ? store.unshift(extension)
1731
1810
  : store.push(extension)
@@ -1797,27 +1876,44 @@ export class Registry {
1797
1876
  }
1798
1877
 
1799
1878
  store[name] = new ProcessorExtension(kind, processorInstance)
1879
+ store[name]._direct = !this._activating
1800
1880
  return store[name]
1801
1881
  }
1802
1882
 
1803
1883
  /** @internal */
1804
1884
  _reset() {
1885
+ // Keep extensions registered directly (outside a group); only clear group-registered ones.
1886
+ // Extensions tagged with _direct=true survive across activations.
1887
+ const keepArr = (arr) => {
1888
+ const kept = arr?.filter((e) => e._direct) ?? []
1889
+ return kept.length ? kept : null
1890
+ }
1891
+ const keepMap = (map) => {
1892
+ if (!map) return null
1893
+ const kept = Object.create(null)
1894
+ for (const [k, v] of Object.entries(map)) if (v._direct) kept[k] = v
1895
+ return Object.keys(kept).length ? kept : null
1896
+ }
1805
1897
  /** @internal */
1806
- this._preprocessor_extensions = null
1898
+ this._preprocessor_extensions = keepArr(this._preprocessor_extensions)
1807
1899
  /** @internal */
1808
- this._tree_processor_extensions = null
1900
+ this._tree_processor_extensions = keepArr(this._tree_processor_extensions)
1809
1901
  /** @internal */
1810
- this._postprocessor_extensions = null
1902
+ this._postprocessor_extensions = keepArr(this._postprocessor_extensions)
1811
1903
  /** @internal */
1812
- this._include_processor_extensions = null
1904
+ this._include_processor_extensions = keepArr(
1905
+ this._include_processor_extensions
1906
+ )
1813
1907
  /** @internal */
1814
- this._docinfo_processor_extensions = null
1908
+ this._docinfo_processor_extensions = keepArr(
1909
+ this._docinfo_processor_extensions
1910
+ )
1815
1911
  /** @internal */
1816
- this._block_extensions = null
1912
+ this._block_extensions = keepMap(this._block_extensions)
1817
1913
  /** @internal */
1818
- this._block_macro_extensions = null
1914
+ this._block_macro_extensions = keepMap(this._block_macro_extensions)
1819
1915
  /** @internal */
1820
- this._inline_macro_extensions = null
1916
+ this._inline_macro_extensions = keepMap(this._inline_macro_extensions)
1821
1917
  this.document = null
1822
1918
  }
1823
1919
 
@@ -1900,6 +1996,13 @@ export const Extensions = {
1900
1996
  /**
1901
1997
  * Create a new Registry, optionally pre-populated with a named block.
1902
1998
  *
1999
+ * When a `block` is provided it is stored as a group and re-executed on every
2000
+ * activation, making the registry safe to reuse across multiple conversions.
2001
+ * Without a `block`, any extensions registered directly on the returned registry
2002
+ * (e.g. `registry.preprocessor(fn)`) are stored in transient state that is
2003
+ * cleared on every activation — those registrations will be lost from the second
2004
+ * conversion onwards. Prefer the block form when the registry may be reused.
2005
+ *
1903
2006
  * @param {string|null} [name=null] - Optional name for the group; auto-generated if omitted.
1904
2007
  * @param {Function|null} [block=null] - Optional function to register as the group.
1905
2008
  * @returns {Registry}
package/src/index.js CHANGED
@@ -9,7 +9,13 @@ import {
9
9
  ImageReference,
10
10
  RevisionInfo,
11
11
  } from './document.js'
12
- import { Logger, LoggerManager, MemoryLogger, NullLogger } from './logging.js'
12
+ import {
13
+ Logger,
14
+ LoggerManager,
15
+ LogMessage,
16
+ MemoryLogger,
17
+ NullLogger,
18
+ } from './logging.js'
13
19
  import { HttpCache, MemoryHttpCache, HttpCacheManager } from './http_cache.js'
14
20
  import { SafeMode, ContentModel } from './constants.js'
15
21
  import { Timings } from './timings.js'
@@ -122,6 +128,7 @@ export {
122
128
  Reader,
123
129
  SyntaxHighlighterBase,
124
130
  LoggerManager,
131
+ LogMessage,
125
132
  MemoryLogger,
126
133
  NullLogger,
127
134
  HttpCache,
package/src/logging.js CHANGED
@@ -277,14 +277,23 @@ Logger.BasicFormatter = class {
277
277
 
278
278
  Logger.AutoFormattingMessage = {
279
279
  /**
280
- * Attach auto-formatting to any plain object carrying { text, source_location }.
281
- * @param {{text: string, source_location?: string}} obj
280
+ * Attach auto-formatting to any plain object carrying
281
+ * { text, source_location, include_location }.
282
+ *
283
+ * The location(s) are rendered only by inspect()/toString() (used when a
284
+ * stderr Logger formats the line); the structured `source_location` /
285
+ * `include_location` remain on the object so a MemoryLogger can record them
286
+ * on the resulting LogMessage without duplicating them inside `text`.
287
+ * @param {{text: string, source_location?: any, include_location?: any}} obj
282
288
  * @returns {typeof obj} The same object with inspect() and toString() added.
283
289
  */
284
290
  attach(obj) {
285
291
  obj.inspect = function () {
286
292
  const sloc = this.source_location
287
- return sloc ? `${sloc}: ${this.text}` : this.text
293
+ const iloc = this.include_location
294
+ let text = sloc ? `${sloc}: ${this.text}` : this.text
295
+ if (iloc) text += ` (${iloc})`
296
+ return text
288
297
  }
289
298
  obj.toString = obj.inspect
290
299
  return obj
@@ -294,28 +303,46 @@ Logger.AutoFormattingMessage = {
294
303
  // ── LogMessage ────────────────────────────────────────────────────────────────
295
304
 
296
305
  /** Wrapper stored by MemoryLogger; provides getSeverity/getText/getSourceLocation. */
297
- class LogMessage {
306
+ export class LogMessage {
307
+ /**
308
+ * @param {string} severity - Severity label, e.g. 'ERROR'.
309
+ * @param {string|{text: string, source_location?: import('./reader.js').Cursor}|null} message
310
+ */
298
311
  constructor(severity, message) {
299
312
  this.message = message
313
+ /** @type {string} */
300
314
  this.severity = severity // string label, e.g. 'ERROR'
301
315
  // AutoFormattingMessage objects carry { text, source_location }
302
316
  if (message !== null && typeof message === 'object' && 'text' in message) {
303
- this._text = message.text
304
- this._sourceLocation = message.source_location ?? null
317
+ /** @type {string} */
318
+ this.text = message.text
319
+ /** @type {import('./reader.js').Cursor|null} */
320
+ this.sourceLocation = message.source_location ?? null
305
321
  } else {
306
- this._text = message != null ? String(message) : ''
307
- this._sourceLocation = null
322
+ this.text = message != null ? String(message) : ''
323
+ this.sourceLocation = null
308
324
  }
309
325
  }
310
326
 
327
+ /**
328
+ * @returns {string} The severity label, e.g. 'ERROR'.
329
+ */
311
330
  getSeverity() {
312
331
  return this.severity
313
332
  }
333
+
334
+ /**
335
+ * @returns {string} The message text.
336
+ */
314
337
  getText() {
315
- return this._text
338
+ return this.text
316
339
  }
340
+
341
+ /**
342
+ * @returns {import('./reader.js').Cursor|undefined} The source location, if any.
343
+ */
317
344
  getSourceLocation() {
318
- return this._sourceLocation ?? undefined
345
+ return this.sourceLocation ?? undefined
319
346
  }
320
347
  }
321
348
 
@@ -328,6 +355,7 @@ export class MemoryLogger {
328
355
  // matching Ruby's MemoryLogger (level: UNKNOWN). The add() method stores all
329
356
  // messages unconditionally — level is only used by the isDebug() guard.
330
357
  this.level = Severity.UNKNOWN
358
+ /** @type {LogMessage[]} */
331
359
  this.messages = []
332
360
  }
333
361
 
@@ -335,6 +363,9 @@ export class MemoryLogger {
335
363
  return new MemoryLogger()
336
364
  }
337
365
 
366
+ /**
367
+ * @returns {LogMessage[]} The log messages recorded so far, in order.
368
+ */
338
369
  getMessages() {
339
370
  return this.messages
340
371
  }
package/src/parser.js CHANGED
@@ -598,7 +598,12 @@ export class Parser {
598
598
  }
599
599
  }
600
600
  ;(intro ?? section).blocks.push(newBlock)
601
- for (const key of Object.keys(attributes)) delete attributes[key]
601
+ // Reset the shared attributes object for the next block. Use Reflect.ownKeys
602
+ // (not Object.keys) so the Symbol-keyed attribute entries (ATTR_ENTRIES_KEY)
603
+ // are cleared too; otherwise the array of AttributeEntry objects leaks and
604
+ // accumulates across blocks, causing reassigned attributes (e.g. a body-level
605
+ // `:name:` redefined later) to all resolve to the final value at playback time.
606
+ for (const key of Reflect.ownKeys(attributes)) delete attributes[key]
602
607
  }
603
608
  }
604
609
 
@@ -1449,7 +1454,13 @@ export class Parser {
1449
1454
  }
1450
1455
  }
1451
1456
 
1452
- if (Object.keys(attributes).length > 0) block.updateAttributes(attributes)
1457
+ // Reflect.ownKeys (not Object.keys) so a block carrying only Symbol-keyed
1458
+ // attribute entries (ATTR_ENTRIES_KEY) — e.g. an `:attr:` entry immediately
1459
+ // preceding a list or table — still receives them, matching Ruby's
1460
+ // `attributes.empty?` where the `:attribute_entries` key is counted. Without this
1461
+ // the entries are dropped and the attribute is not played back for that block.
1462
+ if (Reflect.ownKeys(attributes).length > 0)
1463
+ block.updateAttributes(attributes)
1453
1464
  block.commitSubs()
1454
1465
 
1455
1466
  if (block.hasSub('callouts')) {
package/src/reader.js CHANGED
@@ -675,22 +675,34 @@ export class Reader {
675
675
  }
676
676
 
677
677
  /** @param {string} msg @param {{ sourceLocation?: any, includeLocation?: any }} [opts] */
678
- _logWarn(msg, { sourceLocation, includeLocation } = {}) {
679
- let text = sourceLocation ? `${sourceLocation.lineInfo}: ${msg}` : msg
680
- if (includeLocation) text += ` (${includeLocation.lineInfo})`
681
- this.logger.warn(text)
678
+ _logWarn(msg, opts = {}) {
679
+ this.logger.warn(this._messageWithContext(msg, opts))
682
680
  }
683
681
  _logError(msg, opts = {}) {
684
- let text = opts.sourceLocation
685
- ? `${opts.sourceLocation.lineInfo}: ${msg}`
686
- : msg
687
- if (opts.includeLocation) text += ` (${opts.includeLocation.lineInfo})`
688
- this.logger.error(text)
682
+ this.logger.error(this._messageWithContext(msg, opts))
689
683
  }
690
684
  /** @param {string} msg @param {{ sourceLocation?: any }} [opts] */
691
- _logInfo(msg, { sourceLocation } = {}) {
692
- const text = sourceLocation ? `${sourceLocation.lineInfo}: ${msg}` : msg
693
- this.logger.info(text)
685
+ _logInfo(msg, opts = {}) {
686
+ this.logger.info(this._messageWithContext(msg, opts))
687
+ }
688
+
689
+ /**
690
+ * Build an auto-formatting message that keeps the cursor as a structured
691
+ * source_location (rather than baking it into the text). When displayed by a
692
+ * stderr Logger the location is rendered as a "<path>: line <N>: " prefix, but
693
+ * a MemoryLogger records it separately on the LogMessage so consumers can call
694
+ * getSourceLocation(). Mirrors Ruby's Logging#message_with_context.
695
+ * @param {string} msg
696
+ * @param {{ sourceLocation?: any, includeLocation?: any }} [opts]
697
+ * @internal
698
+ */
699
+ _messageWithContext(msg, { sourceLocation, includeLocation } = {}) {
700
+ if (!sourceLocation && !includeLocation) return msg
701
+ return Logger.AutoFormattingMessage.attach({
702
+ text: msg,
703
+ source_location: sourceLocation ?? null,
704
+ include_location: includeLocation ?? null,
705
+ })
694
706
  }
695
707
  }
696
708
 
@@ -1448,7 +1460,7 @@ export class PreprocessorReader extends Reader {
1448
1460
  }
1449
1461
 
1450
1462
  if (isUriish(target) || typeof this._dir !== 'string') {
1451
- if (!doc.getAttribute('allow-uri-read')) {
1463
+ if (!doc.hasAttribute('allow-uri-read')) {
1452
1464
  this._logWarn(
1453
1465
  `cannot include contents of URI: ${target} (allow-uri-read attribute not enabled)`,
1454
1466
  { sourceLocation: this.cursor }
@@ -176,7 +176,7 @@ export const Substitutors = {
176
176
  *
177
177
  * @param {string|string[]} text - The text to process; must not be null.
178
178
  * @param {string[]} [subs=NORMAL_SUBS] - The substitutions to perform.
179
- * @returns {string|string[]} Text with substitutions applied.
179
+ * @returns {Promise<string|string[]>} Text with substitutions applied.
180
180
  */
181
181
  async applySubs(text, subs = NORMAL_SUBS) {
182
182
  const isEmpty = Array.isArray(text) ? text.length === 0 : text.length === 0
@@ -290,7 +290,7 @@ export const Substitutors = {
290
290
  * Substitute quoted text (emphasis, strong, monospaced, etc.)
291
291
  *
292
292
  * @param {string} text
293
- * @returns {string}
293
+ * @returns {Promise<string>}
294
294
  */
295
295
  async subQuotes(text) {
296
296
  const compat = this.document.compatMode
@@ -462,7 +462,7 @@ export const Substitutors = {
462
462
  * Substitute inline macros (links, images, etc.)
463
463
  *
464
464
  * @param {string} text
465
- * @returns {string}
465
+ * @returns {Promise<string>}
466
466
  */
467
467
  async subMacros(text) {
468
468
  const foundSquareBracket = text.includes('[')
@@ -1357,7 +1357,7 @@ export const Substitutors = {
1357
1357
  * Substitute post replacements (hard line breaks).
1358
1358
  *
1359
1359
  * @param {string} text
1360
- * @returns {string}
1360
+ * @returns {Promise<string>}
1361
1361
  */
1362
1362
  async subPostReplacements(text) {
1363
1363
  if (
@@ -1395,7 +1395,7 @@ export const Substitutors = {
1395
1395
  *
1396
1396
  * @param {string} source
1397
1397
  * @param {boolean} processCallouts
1398
- * @returns {string}
1398
+ * @returns {Promise<string>}
1399
1399
  */
1400
1400
  async subSource(source, processCallouts) {
1401
1401
  return processCallouts
@@ -1407,7 +1407,7 @@ export const Substitutors = {
1407
1407
  * Substitute callout source references.
1408
1408
  *
1409
1409
  * @param {string} text
1410
- * @returns {string}
1410
+ * @returns {Promise<string>}
1411
1411
  */
1412
1412
  async subCallouts(text) {
1413
1413
  const calloutRx = this.hasAttribute('line-comment')
@@ -1436,7 +1436,7 @@ export const Substitutors = {
1436
1436
  *
1437
1437
  * @param {string} source
1438
1438
  * @param {boolean} processCallouts
1439
- * @returns {string}
1439
+ * @returns {Promise<string>}
1440
1440
  */
1441
1441
  async highlightSource(source, processCallouts) {
1442
1442
  const syntaxHl = this.document.syntaxHighlighter
@@ -1782,7 +1782,7 @@ export const Substitutors = {
1782
1782
  * Restore passthrough text by reinserting into placeholder positions.
1783
1783
  *
1784
1784
  * @param {string} text
1785
- * @returns {string}
1785
+ * @returns {Promise<string>}
1786
1786
  */
1787
1787
  async restorePassthroughs(text) {
1788
1788
  if (!text.includes(PASS_START)) return text
@@ -1974,7 +1974,7 @@ export const Substitutors = {
1974
1974
  * @param {string} attrlist
1975
1975
  * @param {string[]} [posattrs=[]]
1976
1976
  * @param {Object} [opts={}]
1977
- * @returns {Object}
1977
+ * @returns {Promise<Object>}
1978
1978
  */
1979
1979
  async parseAttributes(attrlist, posattrs = [], opts = {}) {
1980
1980
  if (!attrlist || attrlist.length === 0) return {}
@@ -38,7 +38,25 @@ export namespace SyntaxProcessorDsl {
38
38
  function resolvesAttributes(...args: any[]): void;
39
39
  }
40
40
  export namespace IncludeProcessorDsl {
41
- function handles(...args: any[]): any;
41
+ /**
42
+ * @overload
43
+ * @param {(target: string) => boolean} fn - Predicate that receives only the include target.
44
+ * @returns {void}
45
+ */
46
+ function handles(fn: (target: string) => boolean): void;
47
+ /**
48
+ * @overload
49
+ * @param {(doc: Document, target: string) => boolean} fn - Predicate that receives the document and the include target.
50
+ * @returns {void}
51
+ */
52
+ function handles(fn: (doc: Document, target: string) => boolean): void;
53
+ /**
54
+ * @overload
55
+ * @param {Document} doc - The document being parsed.
56
+ * @param {string} target - The include target.
57
+ * @returns {boolean}
58
+ */
59
+ function handles(doc: Document, target: string): boolean;
42
60
  }
43
61
  export namespace DocinfoProcessorDsl {
44
62
  function atLocation(value: any): void;
@@ -486,14 +504,38 @@ export class ProcessorExtension extends Extension {
486
504
  processMethod: any;
487
505
  }
488
506
  /**
489
- * A Group registers one or more extensions with a Registry.
507
+ * A Group bundles one or more extension registrations that are re-executed on
508
+ * every document conversion, making the registry safe to reuse.
490
509
  *
491
- * Subclass Group and pass the subclass to Extensions.register(), or call
492
- * the static register() method directly.
510
+ * Subclass Group, override {@link Group#activate}, and either call
511
+ * {@link Group.register} to add it globally or pass the subclass to
512
+ * {@link Extensions.create} / {@link Extensions.register}.
513
+ *
514
+ * @example
515
+ * class MyGroup extends Group {
516
+ * activate (registry) {
517
+ * registry.preprocessor(MyPreprocessor)
518
+ * }
519
+ * }
520
+ * MyGroup.register()
493
521
  */
494
522
  export class Group {
495
- static register(name?: any): void;
496
- activate(_registry: any): void;
523
+ /**
524
+ * Register this Group class globally under the given name.
525
+ *
526
+ * Equivalent to calling `Extensions.register(name, this)`.
527
+ *
528
+ * @param {string|null} [name] - Optional name for the group.
529
+ */
530
+ static register(name?: string | null): void;
531
+ /**
532
+ * Called by {@link Registry#activate} on every document conversion.
533
+ *
534
+ * Override this method to register extensions with the provided registry.
535
+ *
536
+ * @param {Registry} _registry - The registry to register extensions with.
537
+ */
538
+ activate(_registry: Registry): void;
497
539
  }
498
540
  /**
499
541
  * The primary entry point into the extension system.
@@ -501,6 +543,18 @@ export class Group {
501
543
  * Registry holds the extensions which have been registered and activated, has
502
544
  * methods for registering or defining a processor and looks up extensions
503
545
  * stored in the registry during parsing.
546
+ *
547
+ * A registry can be reused across multiple conversions. Extensions registered
548
+ * via a group block (passed to {@link Extensions.create} or
549
+ * {@link Extensions.register}) are re-executed on every activation. Extensions
550
+ * registered directly on the registry instance (e.g. `registry.preprocessor(fn)`)
551
+ * are preserved across activations.
552
+ *
553
+ * @example
554
+ * const registry = Extensions.create('my-ext', function () {
555
+ * this.preprocessor(function () { ... })
556
+ * })
557
+ * // registry can be passed to multiple conversions safely
504
558
  */
505
559
  export class Registry {
506
560
  constructor(groups?: {});
@@ -826,6 +880,13 @@ export namespace Extensions {
826
880
  /**
827
881
  * Create a new Registry, optionally pre-populated with a named block.
828
882
  *
883
+ * When a `block` is provided it is stored as a group and re-executed on every
884
+ * activation, making the registry safe to reuse across multiple conversions.
885
+ * Without a `block`, any extensions registered directly on the returned registry
886
+ * (e.g. `registry.preprocessor(fn)`) are stored in transient state that is
887
+ * cleared on every activation — those registrations will be lost from the second
888
+ * conversion onwards. Prefer the block form when the registry may be reused.
889
+ *
829
890
  * @param {string|null} [name=null] - Optional name for the group; auto-generated if omitted.
830
891
  * @param {Function|null} [block=null] - Optional function to register as the group.
831
892
  * @returns {Registry}
@@ -1032,6 +1093,7 @@ export type SyntaxProcessorDslInterface = ProcessorDslInterface & {
1032
1093
  * DSL interface for include processors.
1033
1094
  */
1034
1095
  export type IncludeProcessorDslInterface = DocumentProcessorDslInterface & {
1096
+ handles(fn: (target: string) => boolean): void;
1035
1097
  handles(fn: (doc: Document, target: string) => boolean): void;
1036
1098
  };
1037
1099
  /**
package/types/index.d.cts CHANGED
@@ -54,6 +54,7 @@ import { Section } from './section.js';
54
54
  import { Reader } from './reader.js';
55
55
  import { SyntaxHighlighterBase } from './syntax_highlighter.js';
56
56
  import { LoggerManager } from './logging.js';
57
+ import { LogMessage } from './logging.js';
57
58
  import { MemoryLogger } from './logging.js';
58
59
  import { NullLogger } from './logging.js';
59
60
  import { HttpCache } from './http_cache.js';
@@ -83,4 +84,4 @@ import { deriveBackendTraits } from './converter.js';
83
84
  import { DefaultFactory as DefaultSyntaxHighlighterFactory } from './syntax_highlighter.js';
84
85
  import Html5Converter from './converter/html5.js';
85
86
  import { SyntaxHighlighter } from './syntax_highlighter.js';
86
- export { convert, convertFile, Document, DocumentTitle, Author, Footnote, ImageReference, RevisionInfo, Logger, AbstractNode, AbstractBlock, Inline, Block, List, ListItem, Section, Reader, SyntaxHighlighterBase, LoggerManager, MemoryLogger, NullLogger, HttpCache, MemoryHttpCache, HttpCacheManager, SafeMode, ContentModel, Timings, Registry, Processor, ProcessorExtension, Preprocessor, TreeProcessor, Postprocessor, IncludeProcessor, DocinfoProcessor, BlockProcessor, InlineMacroProcessor, BlockMacroProcessor, Extensions, Cursor, Converter as ConverterFactory, ConverterBase, CustomFactory as ConverterCustomFactory, DefaultConverterFactory, deriveBackendTraits, DefaultSyntaxHighlighterFactory, Html5Converter, SyntaxHighlighter };
87
+ export { convert, convertFile, Document, DocumentTitle, Author, Footnote, ImageReference, RevisionInfo, Logger, AbstractNode, AbstractBlock, Inline, Block, List, ListItem, Section, Reader, SyntaxHighlighterBase, LoggerManager, LogMessage, MemoryLogger, NullLogger, HttpCache, MemoryHttpCache, HttpCacheManager, SafeMode, ContentModel, Timings, Registry, Processor, ProcessorExtension, Preprocessor, TreeProcessor, Postprocessor, IncludeProcessor, DocinfoProcessor, BlockProcessor, InlineMacroProcessor, BlockMacroProcessor, Extensions, Cursor, Converter as ConverterFactory, ConverterBase, CustomFactory as ConverterCustomFactory, DefaultConverterFactory, deriveBackendTraits, DefaultSyntaxHighlighterFactory, Html5Converter, SyntaxHighlighter };
package/types/index.d.ts CHANGED
@@ -53,6 +53,7 @@ import { Section } from './section.js';
53
53
  import { Reader } from './reader.js';
54
54
  import { SyntaxHighlighterBase } from './syntax_highlighter.js';
55
55
  import { LoggerManager } from './logging.js';
56
+ import { LogMessage } from './logging.js';
56
57
  import { MemoryLogger } from './logging.js';
57
58
  import { NullLogger } from './logging.js';
58
59
  import { HttpCache } from './http_cache.js';
@@ -82,4 +83,4 @@ import { deriveBackendTraits } from './converter.js';
82
83
  import { DefaultFactory as DefaultSyntaxHighlighterFactory } from './syntax_highlighter.js';
83
84
  import Html5Converter from './converter/html5.js';
84
85
  import { SyntaxHighlighter } from './syntax_highlighter.js';
85
- export { convert, convertFile, Document, DocumentTitle, Author, Footnote, ImageReference, RevisionInfo, Logger, AbstractNode, AbstractBlock, Inline, Block, List, ListItem, Section, Reader, SyntaxHighlighterBase, LoggerManager, MemoryLogger, NullLogger, HttpCache, MemoryHttpCache, HttpCacheManager, SafeMode, ContentModel, Timings, Registry, Processor, ProcessorExtension, Preprocessor, TreeProcessor, Postprocessor, IncludeProcessor, DocinfoProcessor, BlockProcessor, InlineMacroProcessor, BlockMacroProcessor, Extensions, Cursor, Converter as ConverterFactory, ConverterBase, CustomFactory as ConverterCustomFactory, DefaultConverterFactory, deriveBackendTraits, DefaultSyntaxHighlighterFactory, Html5Converter, SyntaxHighlighter };
86
+ export { convert, convertFile, Document, DocumentTitle, Author, Footnote, ImageReference, RevisionInfo, Logger, AbstractNode, AbstractBlock, Inline, Block, List, ListItem, Section, Reader, SyntaxHighlighterBase, LoggerManager, LogMessage, MemoryLogger, NullLogger, HttpCache, MemoryHttpCache, HttpCacheManager, SafeMode, ContentModel, Timings, Registry, Processor, ProcessorExtension, Preprocessor, TreeProcessor, Postprocessor, IncludeProcessor, DocinfoProcessor, BlockProcessor, InlineMacroProcessor, BlockMacroProcessor, Extensions, Cursor, Converter as ConverterFactory, ConverterBase, CustomFactory as ConverterCustomFactory, DefaultConverterFactory, deriveBackendTraits, DefaultSyntaxHighlighterFactory, Html5Converter, SyntaxHighlighter };