agent-sanitizer 2.64.1 → 2.64.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  Third-party code redistributed in this package.
2
2
 
3
- src/vendor/gfm-autolink-literal.mjs
4
- Copied from micromark-extension-gfm-autolink-literal@2.1.0, with one
5
- function changed (see that file's header). Original licence follows.
3
+ src/vendor/gfm-autolink-literal.mjs (generated, not tracked)
4
+ Generated at install and pack time from micromark-extension-gfm-autolink-literal@2.1.0
5
+ with one function changed by scripts/patches/ (see scripts/gen-gfm-autolink.mjs).
6
+ Original licence follows.
6
7
 
7
8
  (The MIT License)
8
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sanitizer",
3
- "version": "2.64.1",
3
+ "version": "2.64.2",
4
4
  "description": "Defend an agent against hidden-content injection: strip payload-capable invisible Unicode and ANSI, splice out human-invisible HTML, and flag data-exfil URLs in untrusted text before any model sees it.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -58,12 +58,14 @@
58
58
  "agent-sanitizer": "link:.",
59
59
  "bun": "1.3.11",
60
60
  "c8": "11.0.0",
61
+ "diff": "9.0.0",
61
62
  "esbuild": "0.28.1",
62
63
  "eslint": "10.4.0",
63
64
  "eslint-plugin-redos": "4.5.0",
64
65
  "fast-check": "4.8.0",
65
66
  "globals": "17.6.0",
66
67
  "lint-staged": "^17.0.5",
68
+ "micromark-extension-gfm-autolink-literal": "2.1.0",
67
69
  "prettier": "^3.0.0",
68
70
  "rehype-parse": "9.0.1",
69
71
  "remark-gfm": "4.0.1",
@@ -249,6 +251,7 @@
249
251
  "build:types": "tsc -p tsconfig.build.json && tsc -p tsconfig.build-hooks.json",
250
252
  "gen:joining-type": "node scripts/gen-joining-type.mjs",
251
253
  "gen:unicode": "node scripts/gen-unicode.mjs",
254
+ "gen:gfm-autolink": "node scripts/gen-gfm-autolink.mjs",
252
255
  "gen:unicode-properties": "node scripts/gen-unicode-properties.mjs",
253
256
  "gen:fail-open-lib": "node scripts/gen-fail-open-lib.mjs",
254
257
  "gen:node-floor-lib": "node scripts/gen-node-floor-lib.mjs",
package/src/gfm.mjs CHANGED
@@ -4,8 +4,9 @@
4
4
  * This is `remark-gfm@4.0.1`'s own plugin body and
5
5
  * `micromark-extension-gfm@3.0.0`'s own `gfm()` composition, inlined so that
6
6
  * `gfmAutolinkLiteral` can come from `./vendor/gfm-autolink-literal.mjs`
7
- * instead of the published package — see that file for what differs and when
8
- * to delete both. Every other extension, and the mdast layer, are the upstream
7
+ * instead of the published package. That module is generated, not committed:
8
+ * `scripts/gen-gfm-autolink.mjs` patches the pinned upstream at install time,
9
+ * and its header says what differs and when to delete both. Every other extension, and the mdast layer, are the upstream
9
10
  * ones at the versions `remark-gfm` pins.
10
11
  *
11
12
  * Assembling the list here rather than adding an extension beside `remark-gfm`
@@ -168,6 +168,30 @@ export const CONVERT_DEPTH = 128;
168
168
  */
169
169
  export const MAX_HTML_DEPTH = 4096;
170
170
 
171
+ /**
172
+ * Which elements `hast-util-from-parse5` converts as a template: hastscript reads
173
+ * the tag name as a selector (`template.x` is a `template` with class `x`), so
174
+ * the name up to its first `#` or `.`, lowercased.
175
+ *
176
+ * WHY IT MATTERS — the converter then recurses into the parse5 node's `content`,
177
+ * which only an HTML-namespace `<template>` has. A `template` inside SVG or
178
+ * MathML, or an HTML `template.x` tag, has none, the converter throws,
179
+ * and the whole document would be withheld over a benign element.
180
+ */
181
+ const CONVERTS_AS_TEMPLATE = /^template(?:[#.]|$)/i;
182
+
183
+ /**
184
+ * Prefixed to such an element's name for the conversion, and stripped after.
185
+ *
186
+ * A space, because no parsed element's name can hold one (the tokenizer ends a
187
+ * tag name at whitespace), so a masked name collides with nothing, and the
188
+ * converter neither drops it (its selector parse splits on `#` and `.` only) nor
189
+ * changes it (lowercasing, the SVG case map). The element then converts the way
190
+ * any element without template content does: its children as `children`, where
191
+ * every scan of the hast tree reads them, which is also where the DOM has them.
192
+ */
193
+ const TEMPLATE_MASK = " ";
194
+
171
195
  /**
172
196
  * `fromParse5(tree, { file })`, recursing at most `chunkDepth` levels at a time.
173
197
  *
@@ -177,7 +201,10 @@ export const MAX_HTML_DEPTH = 4096;
177
201
  * child its parent's conversion reads (an unclosed element ends where its last
178
202
  * child does). One walk then swaps each placeholder for its subtree. The result
179
203
  * is the tree a single `fromParse5` call returns, which
180
- * `html-tree-adapter.test` holds it to.
204
+ * `html-tree-adapter.test` holds it to — except that an element the converter
205
+ * would take for a template with no content to take (see
206
+ * {@link CONVERTS_AS_TEMPLATE}) converts as an ordinary element instead of
207
+ * throwing.
181
208
  *
182
209
  * Mutates `tree`: {@link parseHtmlFragment} owns the parse and drops it after.
183
210
  * @param {any} tree
@@ -189,6 +216,7 @@ export function fromParse5Bounded(tree, file, chunkDepth = CONVERT_DEPTH) {
189
216
  /** @type {Array<{ siblings: any[], index: number }>} */
190
217
  const cuts = [];
191
218
  const stack = [{ node: tree, depth: 0 }];
219
+ let masked = false;
192
220
  while (stack.length > 0) {
193
221
  const { node, depth } = /** @type {{ node: any, depth: number }} */ (
194
222
  stack.pop()
@@ -197,6 +225,13 @@ export function fromParse5Bounded(tree, file, chunkDepth = CONVERT_DEPTH) {
197
225
  throw new RangeError(`HTML nested deeper than ${MAX_HTML_DEPTH} levels`);
198
226
  // A template's content is a root its template's conversion recurses into.
199
227
  if (node.content) stack.push({ node: node.content, depth: depth + 1 });
228
+ else if (
229
+ node.tagName !== undefined &&
230
+ CONVERTS_AS_TEMPLATE.test(node.tagName)
231
+ ) {
232
+ node.tagName = TEMPLATE_MASK + node.tagName;
233
+ masked = true;
234
+ }
200
235
  const siblings = node.childNodes ?? [];
201
236
  const childDepth = depth + 1;
202
237
  for (let index = 0; index < siblings.length; index++) {
@@ -222,8 +257,10 @@ export function fromParse5Bounded(tree, file, chunkDepth = CONVERT_DEPTH) {
222
257
  const root = fromParse5(tree, { file });
223
258
  /** @type {any[]} */
224
259
  const pending = [root];
225
- while (grafts.size > 0 && pending.length > 0) {
260
+ while ((grafts.size > 0 || masked) && pending.length > 0) {
226
261
  const node = pending.pop();
262
+ if (masked && node.tagName?.startsWith(TEMPLATE_MASK))
263
+ node.tagName = node.tagName.slice(TEMPLATE_MASK.length);
227
264
  if (node.content) pending.push(node.content);
228
265
  const children = node.children ?? [];
229
266
  for (let i = 0; i < children.length; i++) {
@@ -1,81 +1,52 @@
1
1
  /**
2
- * GFM autolink literals for micromark, vendored from
3
- * `micromark-extension-gfm-autolink-literal@2.1.0` (MIT, (c) 2020 Titus
4
- * Wormer). The licence is in `LICENSE-THIRD-PARTY` at the repository root.
2
+ * GENERATED by scripts/gen-gfm-autolink.mjs at install, pack and build time;
3
+ * git-ignored, never committed — DO NOT EDIT.
5
4
  *
6
- * WHY A COPY. `previousUnbalanced` below decides whether an autolink literal
7
- * may start at this position, and upstream answers it by walking back over
8
- * every event the document has produced so far. It memoizes only the "no"
9
- * answer, so one unclosed `[` — a JSON array is the everyday shape, since its
10
- * `]` is the last byte — makes the answer "yes" for the rest of the document
11
- * and costs one full walk per word character. 172 KB of prose behind a single
12
- * `[` took 20.2s to parse; the same text takes 0.17s here. Layer 3 reads its
13
- * URLs off this parse, so the cost lands on every `detectExfil` call.
14
- *
15
- * WHAT CHANGED. One function. `previousUnbalanced` now also records where the
16
- * walk stopped, and a later walk resumes from that index rather than re-reading
17
- * the run before it. The record is used only while `events[index]` still holds
18
- * the same token, so a resolver that splices events invalidates it and the walk
19
- * falls back to reading everything. Nothing else in this file differs from
20
- * upstream — diff it against the published package to confirm.
21
- *
22
- * WHY NOT A PATCH. A `pnpm` patch fixes this repository's own installs and the
23
- * plugin bundle built from them, and reaches no consumer of the published npm
24
- * package (see the npm-consumer gap in the project history). A copy under
25
- * `src/` ships.
26
- *
27
- * WHEN TO DELETE IT. When a published release of the upstream package carries
28
- * an equivalent fix: drop this file and the assembly in `./gfm.mjs`, and go
29
- * back to `remark-gfm`. `test/gfm-autolink-parity.test.mjs` pins the behaviour
30
- * either way.
5
+ * `micromark-extension-gfm-autolink-literal@2.1.0`'s `lib/syntax.js` (MIT, (c) 2020
6
+ * Titus Wormer; licence in LICENSE-THIRD-PARTY) with the one-function
7
+ * `previousUnbalanced` performance fix from
8
+ * `scripts/patches/micromark-extension-gfm-autolink-literal@2.1.0.patch`. The
9
+ * generator's header says why it is patched and when to delete it.
31
10
  */
32
-
33
11
  /**
34
12
  * @import {Code, ConstructRecord, Event, Extension, Previous, State, TokenizeContext, Tokenizer} from 'micromark-util-types'
35
13
  */
36
14
 
37
- import {
38
- asciiAlpha,
39
- asciiAlphanumeric,
40
- asciiControl,
41
- markdownLineEndingOrSpace,
42
- unicodePunctuation,
43
- unicodeWhitespace,
44
- } from "micromark-util-character";
15
+ import { asciiAlpha, asciiAlphanumeric, asciiControl, markdownLineEndingOrSpace, unicodePunctuation, unicodeWhitespace } from 'micromark-util-character';
45
16
  const wwwPrefix = {
46
17
  tokenize: tokenizeWwwPrefix,
47
- partial: true,
18
+ partial: true
48
19
  };
49
20
  const domain = {
50
21
  tokenize: tokenizeDomain,
51
- partial: true,
22
+ partial: true
52
23
  };
53
24
  const path = {
54
25
  tokenize: tokenizePath,
55
- partial: true,
26
+ partial: true
56
27
  };
57
28
  const trail = {
58
29
  tokenize: tokenizeTrail,
59
- partial: true,
30
+ partial: true
60
31
  };
61
32
  const emailDomainDotTrail = {
62
33
  tokenize: tokenizeEmailDomainDotTrail,
63
- partial: true,
34
+ partial: true
64
35
  };
65
36
  const wwwAutolink = {
66
- name: "wwwAutolink",
37
+ name: 'wwwAutolink',
67
38
  tokenize: tokenizeWwwAutolink,
68
- previous: previousWww,
39
+ previous: previousWww
69
40
  };
70
41
  const protocolAutolink = {
71
- name: "protocolAutolink",
42
+ name: 'protocolAutolink',
72
43
  tokenize: tokenizeProtocolAutolink,
73
- previous: previousProtocol,
44
+ previous: previousProtocol
74
45
  };
75
46
  const emailAutolink = {
76
- name: "emailAutolink",
47
+ name: 'emailAutolink',
77
48
  tokenize: tokenizeEmailAutolink,
78
- previous: previousEmail,
49
+ previous: previousEmail
79
50
  };
80
51
 
81
52
  /** @type {ConstructRecord} */
@@ -91,7 +62,7 @@ const text = {};
91
62
  */
92
63
  export function gfmAutolinkLiteral() {
93
64
  return {
94
- text,
65
+ text
95
66
  };
96
67
  }
97
68
 
@@ -102,8 +73,7 @@ let code = 48;
102
73
  while (code < 123) {
103
74
  text[code] = emailAutolink;
104
75
  code++;
105
- if (code === 58) code = 65;
106
- else if (code === 91) code = 97;
76
+ if (code === 58) code = 65;else if (code === 91) code = 97;
107
77
  }
108
78
  text[43] = emailAutolink;
109
79
  text[45] = emailAutolink;
@@ -150,15 +120,11 @@ function tokenizeEmailAutolink(effects, ok, nok) {
150
120
  * @type {State}
151
121
  */
152
122
  function start(code) {
153
- if (
154
- !gfmAtext(code) ||
155
- !previousEmail.call(self, self.previous) ||
156
- previousUnbalanced(self.events)
157
- ) {
123
+ if (!gfmAtext(code) || !previousEmail.call(self, self.previous) || previousUnbalanced(self.events)) {
158
124
  return nok(code);
159
125
  }
160
- effects.enter("literalAutolink");
161
- effects.enter("literalAutolinkEmail");
126
+ effects.enter('literalAutolink');
127
+ effects.enter('literalAutolinkEmail');
162
128
  return atext(code);
163
129
  }
164
130
 
@@ -201,11 +167,7 @@ function tokenizeEmailAutolink(effects, ok, nok) {
201
167
  function emailDomain(code) {
202
168
  // Dot followed by alphanumerical (not `-` or `_`).
203
169
  if (code === 46) {
204
- return effects.check(
205
- emailDomainDotTrail,
206
- emailDomainAfter,
207
- emailDomainDot,
208
- )(code);
170
+ return effects.check(emailDomainDotTrail, emailDomainAfter, emailDomainDot)(code);
209
171
  }
210
172
 
211
173
  // Alphanumerical, `-`, and `_`.
@@ -254,8 +216,8 @@ function tokenizeEmailAutolink(effects, ok, nok) {
254
216
  // Domain must not be empty, must include a dot, and must end in alphabetical.
255
217
  // Source: <https://github.com/github/cmark-gfm/blob/ef1cfcb/extensions/autolink.c#L332>.
256
218
  if (data && dot && asciiAlpha(self.previous)) {
257
- effects.exit("literalAutolinkEmail");
258
- effects.exit("literalAutolink");
219
+ effects.exit('literalAutolinkEmail');
220
+ effects.exit('literalAutolink');
259
221
  return ok(code);
260
222
  }
261
223
  return nok(code);
@@ -288,22 +250,14 @@ function tokenizeWwwAutolink(effects, ok, nok) {
288
250
  * @type {State}
289
251
  */
290
252
  function wwwStart(code) {
291
- if (
292
- (code !== 87 && code !== 119) ||
293
- !previousWww.call(self, self.previous) ||
294
- previousUnbalanced(self.events)
295
- ) {
253
+ if (code !== 87 && code !== 119 || !previousWww.call(self, self.previous) || previousUnbalanced(self.events)) {
296
254
  return nok(code);
297
255
  }
298
- effects.enter("literalAutolink");
299
- effects.enter("literalAutolinkWww");
256
+ effects.enter('literalAutolink');
257
+ effects.enter('literalAutolinkWww');
300
258
  // Note: we *check*, so we can discard the `www.` we parsed.
301
259
  // If it worked, we consider it as a part of the domain.
302
- return effects.check(
303
- wwwPrefix,
304
- effects.attempt(domain, effects.attempt(path, wwwAfter), nok),
305
- nok,
306
- )(code);
260
+ return effects.check(wwwPrefix, effects.attempt(domain, effects.attempt(path, wwwAfter), nok), nok)(code);
307
261
  }
308
262
 
309
263
  /**
@@ -317,8 +271,8 @@ function tokenizeWwwAutolink(effects, ok, nok) {
317
271
  * @type {State}
318
272
  */
319
273
  function wwwAfter(code) {
320
- effects.exit("literalAutolinkWww");
321
- effects.exit("literalAutolink");
274
+ effects.exit('literalAutolinkWww');
275
+ effects.exit('literalAutolink');
322
276
  return ok(code);
323
277
  }
324
278
  }
@@ -336,7 +290,7 @@ function tokenizeWwwAutolink(effects, ok, nok) {
336
290
  */
337
291
  function tokenizeProtocolAutolink(effects, ok, nok) {
338
292
  const self = this;
339
- let buffer = "";
293
+ let buffer = '';
340
294
  let seen = false;
341
295
  return protocolStart;
342
296
 
@@ -351,13 +305,9 @@ function tokenizeProtocolAutolink(effects, ok, nok) {
351
305
  * @type {State}
352
306
  */
353
307
  function protocolStart(code) {
354
- if (
355
- (code === 72 || code === 104) &&
356
- previousProtocol.call(self, self.previous) &&
357
- !previousUnbalanced(self.events)
358
- ) {
359
- effects.enter("literalAutolink");
360
- effects.enter("literalAutolinkHttp");
308
+ if ((code === 72 || code === 104) && previousProtocol.call(self, self.previous) && !previousUnbalanced(self.events)) {
309
+ effects.enter('literalAutolink');
310
+ effects.enter('literalAutolinkHttp');
361
311
  buffer += String.fromCodePoint(code);
362
312
  effects.consume(code);
363
313
  return protocolPrefixInside;
@@ -385,7 +335,7 @@ function tokenizeProtocolAutolink(effects, ok, nok) {
385
335
  }
386
336
  if (code === 58) {
387
337
  const protocol = buffer.toLowerCase();
388
- if (protocol === "http" || protocol === "https") {
338
+ if (protocol === 'http' || protocol === 'https') {
389
339
  effects.consume(code);
390
340
  return protocolSlashesInside;
391
341
  }
@@ -428,17 +378,7 @@ function tokenizeProtocolAutolink(effects, ok, nok) {
428
378
  function afterProtocol(code) {
429
379
  // To do: this is different from `markdown-rs`:
430
380
  // https://github.com/wooorm/markdown-rs/blob/b3a921c761309ae00a51fe348d8a43adbc54b518/src/construct/gfm_autolink_literal.rs#L172-L182
431
- return code === null ||
432
- asciiControl(code) ||
433
- markdownLineEndingOrSpace(code) ||
434
- unicodeWhitespace(code) ||
435
- unicodePunctuation(code)
436
- ? nok(code)
437
- : effects.attempt(
438
- domain,
439
- effects.attempt(path, protocolAfter),
440
- nok,
441
- )(code);
381
+ return code === null || asciiControl(code) || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || unicodePunctuation(code) ? nok(code) : effects.attempt(domain, effects.attempt(path, protocolAfter), nok)(code);
442
382
  }
443
383
 
444
384
  /**
@@ -452,8 +392,8 @@ function tokenizeProtocolAutolink(effects, ok, nok) {
452
392
  * @type {State}
453
393
  */
454
394
  function protocolAfter(code) {
455
- effects.exit("literalAutolinkHttp");
456
- effects.exit("literalAutolink");
395
+ effects.exit('literalAutolinkHttp');
396
+ effects.exit('literalAutolink');
457
397
  return ok(code);
458
398
  }
459
399
  }
@@ -556,12 +496,7 @@ function tokenizeDomain(effects, ok, nok) {
556
496
  // Instead of some new production for Unicode alphanumerics, markdown
557
497
  // already has that for Unicode punctuation and whitespace, so use those.
558
498
  // Source: <https://github.com/github/cmark-gfm/blob/ef1cfcb/extensions/autolink.c#L12>.
559
- if (
560
- code === null ||
561
- markdownLineEndingOrSpace(code) ||
562
- unicodeWhitespace(code) ||
563
- (code !== 45 && unicodePunctuation(code))
564
- ) {
499
+ if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code) || code !== 45 && unicodePunctuation(code)) {
565
500
  return domainAfter(code);
566
501
  }
567
502
  seen = true;
@@ -656,30 +591,10 @@ function tokenizePath(effects, ok) {
656
591
  // Check whether this trailing punctuation marker is optionally
657
592
  // followed by more trailing markers, and then followed
658
593
  // by an end.
659
- if (
660
- code === 33 ||
661
- code === 34 ||
662
- code === 38 ||
663
- code === 39 ||
664
- code === 41 ||
665
- code === 42 ||
666
- code === 44 ||
667
- code === 46 ||
668
- code === 58 ||
669
- code === 59 ||
670
- code === 60 ||
671
- code === 63 ||
672
- code === 93 ||
673
- code === 95 ||
674
- code === 126
675
- ) {
594
+ if (code === 33 || code === 34 || code === 38 || code === 39 || code === 41 || code === 42 || code === 44 || code === 46 || code === 58 || code === 59 || code === 60 || code === 63 || code === 93 || code === 95 || code === 126) {
676
595
  return effects.check(trail, ok, pathAtPunctuation)(code);
677
596
  }
678
- if (
679
- code === null ||
680
- markdownLineEndingOrSpace(code) ||
681
- unicodeWhitespace(code)
682
- ) {
597
+ if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
683
598
  return ok(code);
684
599
  }
685
600
  effects.consume(code);
@@ -736,20 +651,7 @@ function tokenizeTrail(effects, ok, nok) {
736
651
  */
737
652
  function trail(code) {
738
653
  // Regular trailing punctuation.
739
- if (
740
- code === 33 ||
741
- code === 34 ||
742
- code === 39 ||
743
- code === 41 ||
744
- code === 42 ||
745
- code === 44 ||
746
- code === 46 ||
747
- code === 58 ||
748
- code === 59 ||
749
- code === 63 ||
750
- code === 95 ||
751
- code === 126
752
- ) {
654
+ if (code === 33 || code === 34 || code === 39 || code === 41 || code === 42 || code === 44 || code === 46 || code === 58 || code === 59 || code === 63 || code === 95 || code === 126) {
753
655
  effects.consume(code);
754
656
  return trail;
755
657
  }
@@ -770,13 +672,10 @@ function tokenizeTrail(effects, ok, nok) {
770
672
  return trailBracketAfter;
771
673
  }
772
674
  if (
773
- // `<` is an end.
774
- code === 60 ||
775
- // So is whitespace.
776
- code === null ||
777
- markdownLineEndingOrSpace(code) ||
778
- unicodeWhitespace(code)
779
- ) {
675
+ // `<` is an end.
676
+ code === 60 ||
677
+ // So is whitespace.
678
+ code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
780
679
  return ok(code);
781
680
  }
782
681
  return nok(code);
@@ -798,13 +697,7 @@ function tokenizeTrail(effects, ok, nok) {
798
697
  function trailBracketAfter(code) {
799
698
  // Whitespace or something that could start a resource or reference is the end.
800
699
  // Switch back to trail otherwise.
801
- if (
802
- code === null ||
803
- code === 40 ||
804
- code === 91 ||
805
- markdownLineEndingOrSpace(code) ||
806
- unicodeWhitespace(code)
807
- ) {
700
+ if (code === null || code === 40 || code === 91 || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
808
701
  return ok(code);
809
702
  }
810
703
  return trail(code);
@@ -908,16 +801,7 @@ function tokenizeEmailDomainDotTrail(effects, ok, nok) {
908
801
  * @type {Previous}
909
802
  */
910
803
  function previousWww(code) {
911
- return (
912
- code === null ||
913
- code === 40 ||
914
- code === 42 ||
915
- code === 95 ||
916
- code === 91 ||
917
- code === 93 ||
918
- code === 126 ||
919
- markdownLineEndingOrSpace(code)
920
- );
804
+ return code === null || code === 40 || code === 42 || code === 95 || code === 91 || code === 93 || code === 126 || markdownLineEndingOrSpace(code);
921
805
  }
922
806
 
923
807
  /**
@@ -947,13 +831,7 @@ function previousEmail(code) {
947
831
  * @returns {boolean}
948
832
  */
949
833
  function gfmAtext(code) {
950
- return (
951
- code === 43 ||
952
- code === 45 ||
953
- code === 46 ||
954
- code === 95 ||
955
- asciiAlphanumeric(code)
956
- );
834
+ return code === 43 || code === 45 || code === 46 || code === 95 || asciiAlphanumeric(code);
957
835
  }
958
836
 
959
837
  /**
@@ -965,10 +843,7 @@ function previousUnbalanced(events) {
965
843
  let result = false;
966
844
  while (index--) {
967
845
  const token = events[index][1];
968
- if (
969
- (token.type === "labelLink" || token.type === "labelImage") &&
970
- !token._balanced
971
- ) {
846
+ if ((token.type === 'labelLink' || token.type === 'labelImage') && !token._balanced) {
972
847
  result = true;
973
848
  break;
974
849
  }
@@ -30,7 +30,10 @@ export function parseHtmlFragment(html: string): any;
30
30
  * child its parent's conversion reads (an unclosed element ends where its last
31
31
  * child does). One walk then swaps each placeholder for its subtree. The result
32
32
  * is the tree a single `fromParse5` call returns, which
33
- * `html-tree-adapter.test` holds it to.
33
+ * `html-tree-adapter.test` holds it to — except that an element the converter
34
+ * would take for a template with no content to take (see
35
+ * {@link CONVERTS_AS_TEMPLATE}) converts as an ordinary element instead of
36
+ * throwing.
34
37
  *
35
38
  * Mutates `tree`: {@link parseHtmlFragment} owns the parse and drops it after.
36
39
  * @param {any} tree