@asciidoctor/core 4.0.0-alpha.6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- var version = "4.0.0-alpha.6";
1
+ var version = "4.0.0";
2
2
  const packageJson = {
3
3
  version: version};
4
4
 
@@ -13466,6 +13466,7 @@ function _uniform(str, chr, len) {
13466
13466
  * DSL interface for include processors.
13467
13467
  *
13468
13468
  * @typedef {DocumentProcessorDslInterface & {
13469
+ * handles(fn: (target: string) => boolean): void;
13469
13470
  * handles(fn: (doc: Document, target: string) => boolean): void;
13470
13471
  * }} IncludeProcessorDslInterface
13471
13472
  */
@@ -13516,7 +13517,7 @@ function _uniform(str, chr, len) {
13516
13517
  * - Called with a single Function argument → stores it as the process block.
13517
13518
  * - Called with non-Function arguments → invokes the stored process block.
13518
13519
  *
13519
- * The this context inside a stored process function is bound to the processor
13520
+ * The `this` context inside a stored process function is bound to the processor
13520
13521
  * instance at definition time.
13521
13522
  */
13522
13523
  const ProcessorDsl = {
@@ -13690,6 +13691,35 @@ const SyntaxProcessorDsl = {
13690
13691
  const IncludeProcessorDsl = {
13691
13692
  ...DocumentProcessorDsl,
13692
13693
 
13694
+ /**
13695
+ * @overload
13696
+ * @param {(target: string) => boolean} fn - Predicate that receives only the include target.
13697
+ * @returns {void}
13698
+ */
13699
+ /**
13700
+ * @overload
13701
+ * @param {(doc: Document, target: string) => boolean} fn - Predicate that receives the document and the include target.
13702
+ * @returns {void}
13703
+ */
13704
+ /**
13705
+ * @overload
13706
+ * @param {Document} doc - The document being parsed.
13707
+ * @param {string} target - The include target.
13708
+ * @returns {boolean}
13709
+ */
13710
+ /**
13711
+ * Register a function that decides whether this include processor handles a given target,
13712
+ * or invoke the registered handler.
13713
+ *
13714
+ * **Setter form** — register a predicate. The callback may accept either just the target
13715
+ * string (arity 1) or both the document and the target string (arity 2).
13716
+ *
13717
+ * **Invoker form** — call the registered predicate with `(doc, target)` and return its
13718
+ * result, or return `true` if no predicate has been registered.
13719
+ *
13720
+ * @param {...*} args
13721
+ * @returns {boolean | void}
13722
+ */
13693
13723
  handles(...args) {
13694
13724
  if (args.length === 1 && typeof args[0] === 'function') {
13695
13725
  const fn = args[0];
@@ -14457,6 +14487,8 @@ class ProcessorExtension extends Extension {
14457
14487
  super(kind, instance, instance.config);
14458
14488
  this.processMethod =
14459
14489
  processMethod || ((...args) => instance.process(...args));
14490
+ /** @internal */
14491
+ this._direct = false;
14460
14492
  }
14461
14493
  }
14462
14494
 
@@ -14484,10 +14516,24 @@ const SYNTAX_PROCESSOR_CLASSES = {
14484
14516
  * Registry holds the extensions which have been registered and activated, has
14485
14517
  * methods for registering or defining a processor and looks up extensions
14486
14518
  * stored in the registry during parsing.
14519
+ *
14520
+ * A registry can be reused across multiple conversions. Extensions registered
14521
+ * via a group block (passed to {@link Extensions.create} or
14522
+ * {@link Extensions.register}) are re-executed on every activation. Extensions
14523
+ * registered directly on the registry instance (e.g. `registry.preprocessor(fn)`)
14524
+ * are preserved across activations.
14525
+ *
14526
+ * @example
14527
+ * const registry = Extensions.create('my-ext', function () {
14528
+ * this.preprocessor(function () { ... })
14529
+ * })
14530
+ * // registry can be passed to multiple conversions safely
14487
14531
  */
14488
14532
  class Registry {
14489
14533
  constructor(groups = {}) {
14490
14534
  this.groups = groups;
14535
+ /** @internal */
14536
+ this._activating = false;
14491
14537
  this._reset();
14492
14538
  }
14493
14539
 
@@ -14504,18 +14550,26 @@ class Registry {
14504
14550
  ...Object.values(Extensions.groups()),
14505
14551
  ...Object.values(this.groups),
14506
14552
  ];
14507
- for (const group of extGroups) {
14508
- if (typeof group === 'function') {
14509
- // Check if it is a class (constructor) with an activate prototype method.
14510
- if (group.prototype && typeof group.prototype.activate === 'function') {
14511
- new group().activate(this);
14512
- } else {
14513
- // Plain function — call in the context of this registry (like instance_exec).
14514
- group.length === 0 ? group.call(this) : group(this);
14553
+ this._activating = true;
14554
+ try {
14555
+ for (const group of extGroups) {
14556
+ if (typeof group === 'function') {
14557
+ // Check if it is a class (constructor) with an activate prototype method.
14558
+ if (
14559
+ group.prototype &&
14560
+ typeof group.prototype.activate === 'function'
14561
+ ) {
14562
+ new group().activate(this);
14563
+ } else {
14564
+ // Plain function — call in the context of this registry (like instance_exec).
14565
+ group.length === 0 ? group.call(this) : group(this);
14566
+ }
14567
+ } else if (group && typeof group.activate === 'function') {
14568
+ group.activate(this);
14515
14569
  }
14516
- } else if (group && typeof group.activate === 'function') {
14517
- group.activate(this);
14518
14570
  }
14571
+ } finally {
14572
+ this._activating = false;
14519
14573
  }
14520
14574
  return this
14521
14575
  }
@@ -15091,6 +15145,7 @@ class Registry {
15091
15145
  }
15092
15146
 
15093
15147
  const extension = new ProcessorExtension(kind, processorInstance);
15148
+ extension._direct = !this._activating;
15094
15149
  extension.config.position === '>>'
15095
15150
  ? store.unshift(extension)
15096
15151
  : store.push(extension);
@@ -15162,27 +15217,44 @@ class Registry {
15162
15217
  }
15163
15218
 
15164
15219
  store[name] = new ProcessorExtension(kind, processorInstance);
15220
+ store[name]._direct = !this._activating;
15165
15221
  return store[name]
15166
15222
  }
15167
15223
 
15168
15224
  /** @internal */
15169
15225
  _reset() {
15226
+ // Keep extensions registered directly (outside a group); only clear group-registered ones.
15227
+ // Extensions tagged with _direct=true survive across activations.
15228
+ const keepArr = (arr) => {
15229
+ const kept = arr?.filter((e) => e._direct) ?? [];
15230
+ return kept.length ? kept : null
15231
+ };
15232
+ const keepMap = (map) => {
15233
+ if (!map) return null
15234
+ const kept = Object.create(null);
15235
+ for (const [k, v] of Object.entries(map)) if (v._direct) kept[k] = v;
15236
+ return Object.keys(kept).length ? kept : null
15237
+ };
15170
15238
  /** @internal */
15171
- this._preprocessor_extensions = null;
15239
+ this._preprocessor_extensions = keepArr(this._preprocessor_extensions);
15172
15240
  /** @internal */
15173
- this._tree_processor_extensions = null;
15241
+ this._tree_processor_extensions = keepArr(this._tree_processor_extensions);
15174
15242
  /** @internal */
15175
- this._postprocessor_extensions = null;
15243
+ this._postprocessor_extensions = keepArr(this._postprocessor_extensions);
15176
15244
  /** @internal */
15177
- this._include_processor_extensions = null;
15245
+ this._include_processor_extensions = keepArr(
15246
+ this._include_processor_extensions
15247
+ );
15178
15248
  /** @internal */
15179
- this._docinfo_processor_extensions = null;
15249
+ this._docinfo_processor_extensions = keepArr(
15250
+ this._docinfo_processor_extensions
15251
+ );
15180
15252
  /** @internal */
15181
- this._block_extensions = null;
15253
+ this._block_extensions = keepMap(this._block_extensions);
15182
15254
  /** @internal */
15183
- this._block_macro_extensions = null;
15255
+ this._block_macro_extensions = keepMap(this._block_macro_extensions);
15184
15256
  /** @internal */
15185
- this._inline_macro_extensions = null;
15257
+ this._inline_macro_extensions = keepMap(this._inline_macro_extensions);
15186
15258
  this.document = null;
15187
15259
  }
15188
15260
 
@@ -15265,6 +15337,13 @@ const Extensions = {
15265
15337
  /**
15266
15338
  * Create a new Registry, optionally pre-populated with a named block.
15267
15339
  *
15340
+ * When a `block` is provided it is stored as a group and re-executed on every
15341
+ * activation, making the registry safe to reuse across multiple conversions.
15342
+ * Without a `block`, any extensions registered directly on the returned registry
15343
+ * (e.g. `registry.preprocessor(fn)`) are stored in transient state that is
15344
+ * cleared on every activation — those registrations will be lost from the second
15345
+ * conversion onwards. Prefer the block form when the registry may be reused.
15346
+ *
15268
15347
  * @param {string|null} [name=null] - Optional name for the group; auto-generated if omitted.
15269
15348
  * @param {Function|null} [block=null] - Optional function to register as the group.
15270
15349
  * @returns {Registry}
@@ -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.6";
8
+ var version = "4.0.0";
9
9
  const packageJson = {
10
10
  version: version};
11
11
 
@@ -13473,6 +13473,7 @@ function _uniform(str, chr, len) {
13473
13473
  * DSL interface for include processors.
13474
13474
  *
13475
13475
  * @typedef {DocumentProcessorDslInterface & {
13476
+ * handles(fn: (target: string) => boolean): void;
13476
13477
  * handles(fn: (doc: Document, target: string) => boolean): void;
13477
13478
  * }} IncludeProcessorDslInterface
13478
13479
  */
@@ -13523,7 +13524,7 @@ function _uniform(str, chr, len) {
13523
13524
  * - Called with a single Function argument → stores it as the process block.
13524
13525
  * - Called with non-Function arguments → invokes the stored process block.
13525
13526
  *
13526
- * The this context inside a stored process function is bound to the processor
13527
+ * The `this` context inside a stored process function is bound to the processor
13527
13528
  * instance at definition time.
13528
13529
  */
13529
13530
  const ProcessorDsl = {
@@ -13697,6 +13698,35 @@ const SyntaxProcessorDsl = {
13697
13698
  const IncludeProcessorDsl = {
13698
13699
  ...DocumentProcessorDsl,
13699
13700
 
13701
+ /**
13702
+ * @overload
13703
+ * @param {(target: string) => boolean} fn - Predicate that receives only the include target.
13704
+ * @returns {void}
13705
+ */
13706
+ /**
13707
+ * @overload
13708
+ * @param {(doc: Document, target: string) => boolean} fn - Predicate that receives the document and the include target.
13709
+ * @returns {void}
13710
+ */
13711
+ /**
13712
+ * @overload
13713
+ * @param {Document} doc - The document being parsed.
13714
+ * @param {string} target - The include target.
13715
+ * @returns {boolean}
13716
+ */
13717
+ /**
13718
+ * Register a function that decides whether this include processor handles a given target,
13719
+ * or invoke the registered handler.
13720
+ *
13721
+ * **Setter form** — register a predicate. The callback may accept either just the target
13722
+ * string (arity 1) or both the document and the target string (arity 2).
13723
+ *
13724
+ * **Invoker form** — call the registered predicate with `(doc, target)` and return its
13725
+ * result, or return `true` if no predicate has been registered.
13726
+ *
13727
+ * @param {...*} args
13728
+ * @returns {boolean | void}
13729
+ */
13700
13730
  handles(...args) {
13701
13731
  if (args.length === 1 && typeof args[0] === 'function') {
13702
13732
  const fn = args[0];
@@ -14464,6 +14494,8 @@ class ProcessorExtension extends Extension {
14464
14494
  super(kind, instance, instance.config);
14465
14495
  this.processMethod =
14466
14496
  processMethod || ((...args) => instance.process(...args));
14497
+ /** @internal */
14498
+ this._direct = false;
14467
14499
  }
14468
14500
  }
14469
14501
 
@@ -14491,10 +14523,24 @@ const SYNTAX_PROCESSOR_CLASSES = {
14491
14523
  * Registry holds the extensions which have been registered and activated, has
14492
14524
  * methods for registering or defining a processor and looks up extensions
14493
14525
  * stored in the registry during parsing.
14526
+ *
14527
+ * A registry can be reused across multiple conversions. Extensions registered
14528
+ * via a group block (passed to {@link Extensions.create} or
14529
+ * {@link Extensions.register}) are re-executed on every activation. Extensions
14530
+ * registered directly on the registry instance (e.g. `registry.preprocessor(fn)`)
14531
+ * are preserved across activations.
14532
+ *
14533
+ * @example
14534
+ * const registry = Extensions.create('my-ext', function () {
14535
+ * this.preprocessor(function () { ... })
14536
+ * })
14537
+ * // registry can be passed to multiple conversions safely
14494
14538
  */
14495
14539
  class Registry {
14496
14540
  constructor(groups = {}) {
14497
14541
  this.groups = groups;
14542
+ /** @internal */
14543
+ this._activating = false;
14498
14544
  this._reset();
14499
14545
  }
14500
14546
 
@@ -14511,18 +14557,26 @@ class Registry {
14511
14557
  ...Object.values(Extensions.groups()),
14512
14558
  ...Object.values(this.groups),
14513
14559
  ];
14514
- for (const group of extGroups) {
14515
- if (typeof group === 'function') {
14516
- // Check if it is a class (constructor) with an activate prototype method.
14517
- if (group.prototype && typeof group.prototype.activate === 'function') {
14518
- new group().activate(this);
14519
- } else {
14520
- // Plain function — call in the context of this registry (like instance_exec).
14521
- group.length === 0 ? group.call(this) : group(this);
14560
+ this._activating = true;
14561
+ try {
14562
+ for (const group of extGroups) {
14563
+ if (typeof group === 'function') {
14564
+ // Check if it is a class (constructor) with an activate prototype method.
14565
+ if (
14566
+ group.prototype &&
14567
+ typeof group.prototype.activate === 'function'
14568
+ ) {
14569
+ new group().activate(this);
14570
+ } else {
14571
+ // Plain function — call in the context of this registry (like instance_exec).
14572
+ group.length === 0 ? group.call(this) : group(this);
14573
+ }
14574
+ } else if (group && typeof group.activate === 'function') {
14575
+ group.activate(this);
14522
14576
  }
14523
- } else if (group && typeof group.activate === 'function') {
14524
- group.activate(this);
14525
14577
  }
14578
+ } finally {
14579
+ this._activating = false;
14526
14580
  }
14527
14581
  return this
14528
14582
  }
@@ -15098,6 +15152,7 @@ class Registry {
15098
15152
  }
15099
15153
 
15100
15154
  const extension = new ProcessorExtension(kind, processorInstance);
15155
+ extension._direct = !this._activating;
15101
15156
  extension.config.position === '>>'
15102
15157
  ? store.unshift(extension)
15103
15158
  : store.push(extension);
@@ -15169,27 +15224,44 @@ class Registry {
15169
15224
  }
15170
15225
 
15171
15226
  store[name] = new ProcessorExtension(kind, processorInstance);
15227
+ store[name]._direct = !this._activating;
15172
15228
  return store[name]
15173
15229
  }
15174
15230
 
15175
15231
  /** @internal */
15176
15232
  _reset() {
15233
+ // Keep extensions registered directly (outside a group); only clear group-registered ones.
15234
+ // Extensions tagged with _direct=true survive across activations.
15235
+ const keepArr = (arr) => {
15236
+ const kept = arr?.filter((e) => e._direct) ?? [];
15237
+ return kept.length ? kept : null
15238
+ };
15239
+ const keepMap = (map) => {
15240
+ if (!map) return null
15241
+ const kept = Object.create(null);
15242
+ for (const [k, v] of Object.entries(map)) if (v._direct) kept[k] = v;
15243
+ return Object.keys(kept).length ? kept : null
15244
+ };
15177
15245
  /** @internal */
15178
- this._preprocessor_extensions = null;
15246
+ this._preprocessor_extensions = keepArr(this._preprocessor_extensions);
15179
15247
  /** @internal */
15180
- this._tree_processor_extensions = null;
15248
+ this._tree_processor_extensions = keepArr(this._tree_processor_extensions);
15181
15249
  /** @internal */
15182
- this._postprocessor_extensions = null;
15250
+ this._postprocessor_extensions = keepArr(this._postprocessor_extensions);
15183
15251
  /** @internal */
15184
- this._include_processor_extensions = null;
15252
+ this._include_processor_extensions = keepArr(
15253
+ this._include_processor_extensions
15254
+ );
15185
15255
  /** @internal */
15186
- this._docinfo_processor_extensions = null;
15256
+ this._docinfo_processor_extensions = keepArr(
15257
+ this._docinfo_processor_extensions
15258
+ );
15187
15259
  /** @internal */
15188
- this._block_extensions = null;
15260
+ this._block_extensions = keepMap(this._block_extensions);
15189
15261
  /** @internal */
15190
- this._block_macro_extensions = null;
15262
+ this._block_macro_extensions = keepMap(this._block_macro_extensions);
15191
15263
  /** @internal */
15192
- this._inline_macro_extensions = null;
15264
+ this._inline_macro_extensions = keepMap(this._inline_macro_extensions);
15193
15265
  this.document = null;
15194
15266
  }
15195
15267
 
@@ -15272,6 +15344,13 @@ const Extensions = {
15272
15344
  /**
15273
15345
  * Create a new Registry, optionally pre-populated with a named block.
15274
15346
  *
15347
+ * When a `block` is provided it is stored as a group and re-executed on every
15348
+ * activation, making the registry safe to reuse across multiple conversions.
15349
+ * Without a `block`, any extensions registered directly on the returned registry
15350
+ * (e.g. `registry.preprocessor(fn)`) are stored in transient state that is
15351
+ * cleared on every activation — those registrations will be lost from the second
15352
+ * conversion onwards. Prefer the block form when the registry may be reused.
15353
+ *
15275
15354
  * @param {string|null} [name=null] - Optional name for the group; auto-generated if omitted.
15276
15355
  * @param {Function|null} [block=null] - Optional function to register as the group.
15277
15356
  * @returns {Registry}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asciidoctor/core",
3
- "version": "4.0.0-alpha.6",
3
+ "version": "4.0.0",
4
4
  "description": "Asciidoctor.js: AsciiDoc in JavaScript powered by Asciidoctor",
5
5
  "type": "module",
6
6
  "main": "build/node/index.cjs",
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}
@@ -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
  /**