securemark 0.289.5 → 0.290.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/design.md +1 -1
  3. package/dist/index.js +118 -334
  4. package/markdown.d.ts +11 -12
  5. package/package.json +1 -1
  6. package/src/combinator/control/manipulation/surround.ts +20 -30
  7. package/src/combinator/control/monad/bind.ts +1 -0
  8. package/src/combinator/data/parser.ts +0 -1
  9. package/src/parser/api/parse.test.ts +6 -7
  10. package/src/parser/block/extension/example.test.ts +2 -2
  11. package/src/parser/block/extension/fig.test.ts +8 -8
  12. package/src/parser/block/extension/figure.test.ts +14 -14
  13. package/src/parser/block/paragraph.test.ts +4 -0
  14. package/src/parser/context.ts +5 -11
  15. package/src/parser/inline/annotation.ts +5 -5
  16. package/src/parser/inline/autolink/email.ts +11 -5
  17. package/src/parser/inline/autolink/hashtag.ts +4 -2
  18. package/src/parser/inline/autolink/url.test.ts +2 -2
  19. package/src/parser/inline/autolink/url.ts +6 -6
  20. package/src/parser/inline/bracket.test.ts +1 -0
  21. package/src/parser/inline/code.ts +2 -2
  22. package/src/parser/inline/extension/index.ts +5 -5
  23. package/src/parser/inline/extension/label.ts +1 -1
  24. package/src/parser/inline/html.test.ts +4 -4
  25. package/src/parser/inline/html.ts +14 -27
  26. package/src/parser/inline/link.test.ts +17 -17
  27. package/src/parser/inline/link.ts +42 -15
  28. package/src/parser/inline/media.test.ts +45 -46
  29. package/src/parser/inline/media.ts +49 -35
  30. package/src/parser/inline/reference.ts +15 -11
  31. package/src/parser/inline/ruby.ts +15 -12
  32. package/src/parser/inline/shortmedia.test.ts +2 -2
  33. package/src/parser/inline/template.test.ts +2 -2
  34. package/src/parser/inline/template.ts +12 -10
  35. package/src/parser/inline.test.ts +1 -0
  36. package/src/parser/source/escapable.test.ts +5 -5
  37. package/src/parser/source/escapable.ts +2 -3
  38. package/src/parser/source/unescapable.test.ts +5 -5
  39. package/src/parser/source/unescapable.ts +2 -3
  40. package/src/renderer/render/media.test.ts +3 -3
@@ -14,7 +14,7 @@ export const url: AutolinkParser.UrlParser = lazy(() => validate(['http://', 'ht
14
14
  some(unescsource, closer),
15
15
  ]), undefined, [[/^[^\x21-\x7E]/, 3]])),
16
16
  false,
17
- [3 | Backtrack.url]),
17
+ [3 | Backtrack.autolink]),
18
18
  union([
19
19
  constraint(State.autolink, false, state(State.autolink, convert(
20
20
  url => `{ ${url} }`,
@@ -38,15 +38,15 @@ export const lineurl: AutolinkParser.UrlParser.LineUrlParser = lazy(() => open(
38
38
  ]),
39
39
  ])),
40
40
  false,
41
- [3 | Backtrack.linebracket]));
41
+ [3 | Backtrack.autolink]));
42
42
 
43
43
  const bracket: AutolinkParser.UrlParser.BracketParser = lazy(() => union([
44
44
  surround(str('('), recursion(Recursion.terminal, some(union([bracket, unescsource]), ')')), str(')'), true,
45
- undefined, () => [[], ''], [3 | Backtrack.url]),
45
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
46
46
  surround(str('['), recursion(Recursion.terminal, some(union([bracket, unescsource]), ']')), str(']'), true,
47
- undefined, () => [[], ''], [3 | Backtrack.url]),
47
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
48
48
  surround(str('{'), recursion(Recursion.terminal, some(union([bracket, unescsource]), '}')), str('}'), true,
49
- undefined, () => [[], ''], [3 | Backtrack.url]),
49
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
50
50
  surround(str('"'), precedence(2, recursion(Recursion.terminal, some(unescsource, '"'))), str('"'), true,
51
- undefined, () => [[], ''], [3 | Backtrack.url]),
51
+ undefined, () => [[], ''], [3 | Backtrack.autolink]),
52
52
  ]));
@@ -50,6 +50,7 @@ describe('Unit: parser/inline/bracket', () => {
50
50
  assert.deepStrictEqual(inspect(parser('(A、B)')), [['(', 'A、B', ')'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('(<bdi>a\\\nb</bdi>)')), [['<span class="paren">(<bdi>a<br>b</bdi>)</span>'], '']);
52
52
  assert.deepStrictEqual(inspect(parser('([% a\\\nb %])')), [['<span class="paren">(<span class="remark"><input type="checkbox"><span>[% a<br>b %]</span></span>)</span>'], '']);
53
+ assert.deepStrictEqual(inspect(parser('({{\\\n}})')), [['<span class="paren">(<span class="template">{{\\<br>}}</span>)</span>'], '']);
53
54
  });
54
55
 
55
56
  it('[', () => {
@@ -6,13 +6,13 @@ import { html } from 'typed-dom/dom';
6
6
  export const code: CodeParser = validate(
7
7
  ({ source, context }) =>
8
8
  source[0] === '`' &&
9
- !getBacktrack(context, [1 | Backtrack.linebracket], source, source.slice(1)),
9
+ !getBacktrack(context, [1 | Backtrack.bracket], source, source.length - 1),
10
10
  match(
11
11
  /^(`+)(?!`)([^\n]*?)(?:((?<!`)\1(?!`))|$|\n)/,
12
12
  ([whole, , body, closer]) => ({ source, context }) =>
13
13
  closer
14
14
  ? [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)]
15
- : void setBacktrack(context, [2 | Backtrack.linebracket], source),
15
+ : void setBacktrack(context, [2 | Backtrack.bracket], source.length),
16
16
  true));
17
17
 
18
18
  function format(text: string): string {
@@ -1,5 +1,5 @@
1
1
  import { ExtensionParser } from '../../inline';
2
- import { State, Backtrack, BacktrackState, Command } from '../../context';
2
+ import { State, Backtrack, Command } from '../../context';
3
3
  import { eval } from '../../../combinator/data/parser';
4
4
  import { union, inits, some, precedence, state, constraint, validate, surround, lazy, fmap } from '../../../combinator';
5
5
  import { inline } from '../../inline';
@@ -19,16 +19,16 @@ export const index: IndexParser = lazy(() => constraint(State.index, false, fmap
19
19
  some(inits([
20
20
  inline,
21
21
  signature,
22
- ]), ']', [['\n', 9], [']', 1]])))),
22
+ ]), ']', [[']', 1]])))),
23
23
  ']',
24
24
  false,
25
- ([, ns], rest) =>
25
+ ([, ns], rest, context) =>
26
+ context.linebreak === undefined &&
26
27
  trimBlankNodeEnd(ns).length > 0
27
28
  ? [[html('a', { 'data-index': dataindex(ns) }, defrag(ns))], rest]
28
29
  : undefined,
29
30
  undefined,
30
- [3 | Backtrack.linebracket],
31
- Backtrack.bracket | BacktrackState.nobreak)),
31
+ [3 | Backtrack.bracket])),
32
32
  ([el]: [HTMLAnchorElement]) => [
33
33
  define(el,
34
34
  {
@@ -13,7 +13,7 @@ export const segment: ExtensionParser.LabelParser.SegmentParser = clear(union([
13
13
 
14
14
  export const label: ExtensionParser.LabelParser = constraint(State.label, false, fmap(
15
15
  union([
16
- surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.linebracket, 1]),
16
+ surround('[', body, ']', false, undefined, undefined, [1 | Backtrack.bracket, 1]),
17
17
  body,
18
18
  ]),
19
19
  ([text]) => [
@@ -36,16 +36,16 @@ describe('Unit: parser/inline/html', () => {
36
36
  assert.deepStrictEqual(inspect(parser('<bdi>\na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>a&lt;/bdi&gt;</span>'], '']);
37
37
  assert.deepStrictEqual(inspect(parser('<bdi>\\\na</bdi>')), [['<span class="invalid">&lt;bdi&gt;<br>a&lt;/bdi&gt;</span>'], '']);
38
38
  assert.deepStrictEqual(inspect(parser('<bdi>a')), [['<span class="invalid">&lt;bdi&gt;a</span>'], '']);
39
- assert.deepStrictEqual(inspect(parser('<bdi>a</BDO>')), [['<span class="invalid">&lt;bdi&gt;a&lt;/BDO&gt;</span>'], '']);
40
- assert.deepStrictEqual(inspect(parser('<BDI>a</BDI>')), [['<span class="invalid">&lt;BDI&gt;a&lt;/BDI&gt;</span>'], '']);
41
- assert.deepStrictEqual(inspect(parser('<BDI>a</bdo>')), [['<span class="invalid">&lt;BDI&gt;a&lt;/bdo&gt;</span>'], '']);
39
+ assert.deepStrictEqual(inspect(parser('<bdi>a</BDI>')), [['<span class="invalid">&lt;bdi&gt;a&lt;/BDI&gt;</span>'], '']);
40
+ assert.deepStrictEqual(inspect(parser('<BDI>a</BDI>')), [['<span class="invalid">&lt;BDI&gt;</span>'], 'a</BDI>']);
41
+ assert.deepStrictEqual(inspect(parser('<BDI>a</bdi>')), [['<span class="invalid">&lt;BDI&gt;</span>'], 'a</bdi>']);
42
42
  assert.deepStrictEqual(inspect(parser('</bdi>')), undefined);
43
43
  assert.deepStrictEqual(inspect(parser('<bdi/>')), undefined);
44
44
  assert.deepStrictEqual(inspect(parser('<b><b><b>a</b></b></b>')), [['<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;<span class="invalid">&lt;b&gt;a&lt;/b&gt;</span>&lt;/b&gt;</span>&lt;/b&gt;</span>'], '']);
45
45
  assert.deepStrictEqual(inspect(parser('<bdi><bdi><bdi>a</bdi></bdi></bdi>')), [['<bdi><bdi><bdi>a</bdi></bdi></bdi>'], '']);
46
46
  assert.deepStrictEqual(inspect(parser('<x a="*b*"')), undefined);
47
47
  assert.deepStrictEqual(inspect(parser('<x a="*b*">')), [['<span class="invalid">&lt;x a="*b*"&gt;</span>'], '']);
48
- assert.deepStrictEqual(inspect(parser('<x a="*b*">c')), [['<span class="invalid">&lt;x a="*b*"&gt;c</span>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('<x a="*b*">c')), [['<span class="invalid">&lt;x a="*b*"&gt;</span>'], 'c']);
49
49
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*"')), undefined);
50
50
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*">')), [['<span class="invalid">&lt;bdi a="*b*"&gt;</span>'], '']);
51
51
  assert.deepStrictEqual(inspect(parser('<bdi a="*b*">c')), [['<span class="invalid">&lt;bdi a="*b*"&gt;c</span>'], '']);
@@ -6,7 +6,6 @@ import { str } from '../source';
6
6
  import { isLooseNodeStart, blankWith } from '../visibility';
7
7
  import { invalid } from '../util';
8
8
  import { memoize } from 'spica/memoize';
9
- import { Clock } from 'spica/clock';
10
9
  import { unshift, push, splice } from 'spica/array';
11
10
  import { html as h, defrag } from 'typed-dom/dom';
12
11
 
@@ -21,18 +20,17 @@ Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
21
20
 
22
21
  export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
23
22
  union([
24
- focus(
25
- /^<wbr[^\S\n]*>/i,
26
- () => [[h('wbr')], '']),
27
23
  surround(
28
24
  // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
29
25
  str(/^<(?:area|base|br|col|embed|hr|img|input|link|meta|source|track|wbr)(?=[^\S\n]|>)/i),
30
26
  some(union([attribute])),
31
27
  str(/^[^\S\n]*>/), true,
32
28
  ([as, bs = [], cs], rest) =>
33
- [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
29
+ as[0].slice(1) === 'wbr' && bs.length === 0
30
+ ? [[h(as[0].slice(1) as 'wbr')], rest]
31
+ : [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
34
32
  undefined,
35
- [3 | Backtrack.linebracket]),
33
+ [3 | Backtrack.bracket]),
36
34
  match(
37
35
  new RegExp(String.raw`^<(${TAGS.join('|')})(?=[^\S\n]|>)`),
38
36
  memoize(
@@ -40,7 +38,7 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
40
38
  surround<HTMLParser.TagParser, string>(
41
39
  surround(
42
40
  str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
43
- undefined, undefined, [3 | Backtrack.linebracket]),
41
+ undefined, undefined, [3 | Backtrack.bracket]),
44
42
  precedence(3, recursion(Recursion.inline,
45
43
  subsequence([
46
44
  focus(/^[^\S\n]*\n/, some(inline)),
@@ -53,26 +51,15 @@ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i,
53
51
  [[elem(tag, as, bs, [])], rest]),
54
52
  ([, tag]) => tag,
55
53
  new Map())),
56
- match(
57
- /^<([a-z]+)(?=[^\S\n]|>)/i,
58
- memoize(
59
- ([, tag]) =>
60
- surround<HTMLParser.TagParser, string>(
61
- surround(
62
- str(`<${tag}`), some(attribute), str(/^[^\S\n]*>/), true,
63
- undefined, undefined, [3 | Backtrack.linebracket]),
64
- precedence(3, recursion(Recursion.inline,
65
- subsequence([
66
- focus(/^[^\S\n]*\n/, some(inline)),
67
- some(inline, `</${tag}>`, [[`</${tag}>`, 3]]),
68
- ]))),
69
- str(`</${tag}>`), true,
70
- ([as, bs = [], cs], rest) =>
71
- [[elem(tag, as, bs, cs)], rest],
72
- ([as, bs = []], rest) =>
73
- [[elem(tag, as, bs, [])], rest]),
74
- ([, tag]) => tag,
75
- new Clock(10000))),
54
+ surround(
55
+ // https://html.spec.whatwg.org/multipage/syntax.html#void-elements
56
+ str(/^<[a-z]+(?=[^\S\n]|>)/i),
57
+ some(union([attribute])),
58
+ str(/^[^\S\n]*>/), true,
59
+ ([as, bs = [], cs], rest) =>
60
+ [[elem(as[0].slice(1), push(unshift(as, bs), cs), [], [])], rest],
61
+ undefined,
62
+ [3 | Backtrack.bracket]),
76
63
  ])));
77
64
 
78
65
  export const attribute: HTMLParser.AttributeParser = union([
@@ -42,16 +42,16 @@ describe('Unit: parser/inline/link', () => {
42
42
  assert.deepStrictEqual(inspect(parser('[-1234567890]{tel:1234567890}')), [['<a class="invalid">-1234567890</a>'], '']);
43
43
  assert.deepStrictEqual(inspect(parser('[123456789a]{tel:1234567890}')), [['<a class="invalid">123456789a</a>'], '']);
44
44
  assert.deepStrictEqual(inspect(parser('[1234567890]{tel:ttel:1234567890}')), [['<a class="invalid">1234567890</a>'], '']);
45
- //assert.deepStrictEqual(inspect(parser('[#a]{b}')), [['<a class="link" href="b">#a</a>'], '']);
46
- //assert.deepStrictEqual(inspect(parser('[\\#a]{b}')), [['<a class="link" href="b">#a</a>'], '']);
47
- //assert.deepStrictEqual(inspect(parser('[c #a]{b}')), [['<a class="link" href="b">c #a</a>'], '']);
48
- //assert.deepStrictEqual(inspect(parser('[c \\#a]{b}')), [['<a class="link" href="b">c #a</a>'], '']);
49
- //assert.deepStrictEqual(inspect(parser('[]{#a}')), [['<a class="url" href="#a">#a</a>'], '']);
50
- //assert.deepStrictEqual(inspect(parser('[@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
51
- //assert.deepStrictEqual(inspect(parser('[\\@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
52
- //assert.deepStrictEqual(inspect(parser('[c @a]{b}')), [['<a class="link" href="b">c @a</a>'], '']);
53
- //assert.deepStrictEqual(inspect(parser('[c \\@a]{b}')), [['<a class="link" href="b">c @a</a>'], '']);
54
- //assert.deepStrictEqual(inspect(parser('[]{@a}')), [['<a class="url" href="@a">@a</a>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('[#a]{b}')), [['<a class="link" href="b">#a</a>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('[\\#a]{b}')), [['<a class="link" href="b">#a</a>'], '']);
47
+ assert.deepStrictEqual(inspect(parser('[c #a]{b}')), [['<a class="link" href="b">c #a</a>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('[c \\#a]{b}')), [['<a class="link" href="b">c #a</a>'], '']);
49
+ assert.deepStrictEqual(inspect(parser('[]{#a}')), [['<a class="url" href="#a">#a</a>'], '']);
50
+ assert.deepStrictEqual(inspect(parser('[@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
51
+ assert.deepStrictEqual(inspect(parser('[\\@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
52
+ assert.deepStrictEqual(inspect(parser('[c @a]{b}')), [['<a class="link" href="b">c @a</a>'], '']);
53
+ assert.deepStrictEqual(inspect(parser('[c \\@a]{b}')), [['<a class="link" href="b">c @a</a>'], '']);
54
+ assert.deepStrictEqual(inspect(parser('[]{@a}')), [['<a class="url" href="@a">@a</a>'], '']);
55
55
  });
56
56
 
57
57
  it('invalid', () => {
@@ -88,8 +88,8 @@ describe('Unit: parser/inline/link', () => {
88
88
  assert.deepStrictEqual(inspect(parser('[*a\nb*]{/}')), undefined);
89
89
  assert.deepStrictEqual(inspect(parser('[http://host]{http://host}')), [['<a class="invalid">http://host</a>'], '']);
90
90
  assert.deepStrictEqual(inspect(parser('[]{ttp://host}')), [['<a class="invalid">ttp://host</a>'], '']);
91
- //assert.deepStrictEqual(inspect(parser('[]{http://[::ffff:0:0%1]}')), [['<a class="invalid">http://[::ffff:0:0%1]</a>'], '']);
92
- //assert.deepStrictEqual(inspect(parser('[]{http://[::ffff:0:0/96]}')), [['<a class="invalid">http://[::ffff:0:0/96]</a>'], '']);
91
+ assert.deepStrictEqual(inspect(parser('[]{http://[::ffff:0:0%1]}')), [['<a class="invalid">http://[::ffff:0:0%1]</a>'], '']);
92
+ assert.deepStrictEqual(inspect(parser('[]{http://[::ffff:0:0/96]}')), [['<a class="invalid">http://[::ffff:0:0/96]</a>'], '']);
93
93
  assert.deepStrictEqual(inspect(parser('[]{^/.}')), [[`<a class="invalid">^/.</a>`], '']);
94
94
  assert.deepStrictEqual(inspect(parser('[]{^/..}')), [[`<a class="invalid">^/..</a>`], '']);
95
95
  assert.deepStrictEqual(inspect(parser('[]{^/../}')), [[`<a class="invalid">^/../</a>`], '']);
@@ -160,12 +160,12 @@ describe('Unit: parser/inline/link', () => {
160
160
  });
161
161
 
162
162
  it('media', () => {
163
- assert.deepStrictEqual(inspect(parser('[![]{a}]{a}')), [['<a class="link" href="a" target="_blank"><img class="media" data-src="a" alt=""></a>'], '']);
164
- assert.deepStrictEqual(inspect(parser('[![]{a}]{b}')), [['<a class="link" href="b" target="_blank"><img class="media" data-src="a" alt=""></a>'], '']);
163
+ assert.deepStrictEqual(inspect(parser('[![]{a}]{a}')), [['<a class="link" href="a" target="_blank"><img class="media" data-src="a" alt="a"></a>'], '']);
164
+ assert.deepStrictEqual(inspect(parser('[![]{a}]{b}')), [['<a class="link" href="b" target="_blank"><img class="media" data-src="a" alt="a"></a>'], '']);
165
165
  assert.deepStrictEqual(inspect(parser('[![]{a} ]{b}')), [['<a class="link" href="b">![]{a}</a>'], '']);
166
166
  assert.deepStrictEqual(inspect(parser('[![]{a}![]{a}]{b}')), [['<a class="link" href="b">![]{a}![]{a}</a>'], '']);
167
167
  assert.deepStrictEqual(inspect(parser('[[![]{a}]{b}]{c}')), [['<a class="link" href="c">[![]{a}]{b}</a>'], '']);
168
- assert.deepStrictEqual(inspect(parser('[!http://host]{b}')), [['<a class="link" href="b" target="_blank"><img class="media" data-src="http://host" alt=""></a>'], '']);
168
+ assert.deepStrictEqual(inspect(parser('[!http://host]{b}')), [['<a class="link" href="b" target="_blank"><img class="media" data-src="http://host" alt="http://host"></a>'], '']);
169
169
  });
170
170
 
171
171
  it('nest', () => {
@@ -179,7 +179,7 @@ describe('Unit: parser/inline/link', () => {
179
179
  assert.deepStrictEqual(inspect(parser('[[![]{a}]{b}]{b}')), [['<a class="link" href="b">[![]{a}]{b}</a>'], '']);
180
180
  assert.deepStrictEqual(inspect(parser('[((a))]{b}')), [['<a class="link" href="b"><span class="paren">((a))</span></a>'], '']);
181
181
  assert.deepStrictEqual(inspect(parser('[[[a]]]{b}')), [['<a class="link" href="b">[[a]]</a>'], '']);
182
- assert.deepStrictEqual(inspect(parser('[!http://host]{/}')), [['<a class="link" href="/" target="_blank"><img class="media" data-src="http://host" alt=""></a>'], '']);
182
+ assert.deepStrictEqual(inspect(parser('[!http://host]{/}')), [['<a class="link" href="/" target="_blank"><img class="media" data-src="http://host" alt="http://host"></a>'], '']);
183
183
  assert.deepStrictEqual(inspect(parser('[#a]{b}')), [['<a class="link" href="b">#a</a>'], '']);
184
184
  assert.deepStrictEqual(inspect(parser('[@a]{b}')), [['<a class="link" href="b">@a</a>'], '']);
185
185
  assert.deepStrictEqual(inspect(parser('[@a@b]{c}')), [['<a class="link" href="c">@a@b</a>'], '']);
@@ -222,7 +222,7 @@ describe('Unit: parser/inline/link', () => {
222
222
  assert.deepStrictEqual(inspect(parser('[]{ / nofollow}')), [['<a class="url" href="/" rel="nofollow">/</a>'], '']);
223
223
  assert.deepStrictEqual(inspect(parser('[]{ / nofollow }')), [['<a class="url" href="/" rel="nofollow">/</a>'], '']);
224
224
  assert.deepStrictEqual(inspect(parser('[]{http://host nofollow}')), [['<a class="url" href="http://host" target="_blank" rel="nofollow">http://host</a>'], '']);
225
- assert.deepStrictEqual(inspect(parser('[!http://host]{http://host nofollow}')), [['<a class="link" href="http://host" target="_blank" rel="nofollow"><img class="media" data-src="http://host" alt=""></a>'], '']);
225
+ assert.deepStrictEqual(inspect(parser('[!http://host]{http://host nofollow}')), [['<a class="link" href="http://host" target="_blank" rel="nofollow"><img class="media" data-src="http://host" alt="http://host"></a>'], '']);
226
226
  });
227
227
 
228
228
  });
@@ -1,13 +1,14 @@
1
1
  import { MarkdownParser } from '../../../markdown';
2
2
  import { LinkParser } from '../inline';
3
- import { State, Backtrack, BacktrackState } from '../context';
4
- import { union, inits, tails, sequence, some, creation, precedence, state, constraint, validate, surround, open, dup, reverse, lazy, fmap, bind } from '../../combinator';
3
+ import { State, Backtrack, Command } from '../context';
4
+ import { union, inits, tails, sequence, subsequence, some, creation, precedence, state, constraint, validate, surround, open, setBacktrack, dup, reverse, lazy, fmap, bind } from '../../combinator';
5
5
  import { inline, media, shortmedia } from '../inline';
6
6
  import { attributes } from './html';
7
7
  import { linebreak, unescsource, str } from '../source';
8
8
  import { trimBlankStart, trimBlankNodeEnd } from '../visibility';
9
9
  import { invalid, stringify } from '../util';
10
10
  import { ReadonlyURL } from 'spica/url';
11
+ import { push } from 'spica/array';
11
12
  import { html, define, defrag } from 'typed-dom/dom';
12
13
 
13
14
  const optspec = {
@@ -17,22 +18,36 @@ Object.setPrototypeOf(optspec, null);
17
18
 
18
19
  export const textlink: LinkParser.TextLinkParser = lazy(() => constraint(State.link, false, creation(10,
19
20
  precedence(1, state(State.linkers | State.media,
20
- bind(reverse(tails([
21
+ bind(subsequence([
21
22
  dup(surround(
22
23
  '[',
23
- trimBlankStart(some(union([inline]), ']', [['\n', 9], [']', 1]])),
24
+ trimBlankStart(some(union([inline]), ']', [[']', 1]])),
24
25
  ']',
25
- true, undefined, undefined,
26
- [1 | Backtrack.linebracket],
27
- Backtrack.bracket | BacktrackState.nobreak)),
26
+ true,
27
+ ([, ns = []], rest, context) =>
28
+ context.linebreak === undefined
29
+ ? [push(ns, [Command.Escape]), rest]
30
+ : undefined,
31
+ undefined,
32
+ [3 | Backtrack.link, 3 | Backtrack.bracket])),
28
33
  dup(surround(
29
34
  /^{(?![{}])/,
30
35
  inits([uri, some(option)]),
31
36
  /^[^\S\n]*}/,
32
37
  false, undefined, undefined,
33
38
  [3 | Backtrack.link])),
34
- ])),
35
- ([params, content = []]: [string[], (HTMLElement | string)[]], rest, context) => {
39
+ ]),
40
+ ([content, params]: [(HTMLElement | string)[], string[]], rest, context) => {
41
+ if (content.at(-1) === Command.Escape) {
42
+ content.pop();
43
+ if (params === undefined) {
44
+ return void setBacktrack(context, [2 | Backtrack.link], context.recent!.reduce((a, b) => a + b.length, 0));
45
+ }
46
+ }
47
+ else {
48
+ params = content as string[];
49
+ content = [];
50
+ }
36
51
  assert(!html('div', content).querySelector('a, .media, .annotation, .reference'));
37
52
  assert(content[0] !== '');
38
53
  if (content.length !== 0 && trimBlankNodeEnd(content).length === 0) return;
@@ -89,9 +104,14 @@ function parse(
89
104
  const INSECURE_URI = params.shift()!;
90
105
  assert(INSECURE_URI === INSECURE_URI.trim());
91
106
  assert(!INSECURE_URI.match(/\s/));
92
- const uri = new ReadonlyURL(
93
- resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
94
- context.host?.href || location.href);
107
+ let uri: ReadonlyURL | undefined;
108
+ try{
109
+ uri = new ReadonlyURL(
110
+ resolve(INSECURE_URI, context.host ?? location, context.url ?? context.host ?? location),
111
+ context.host?.href || location.href);
112
+ }
113
+ catch {
114
+ }
95
115
  const el = elem(
96
116
  INSECURE_URI,
97
117
  content,
@@ -105,12 +125,16 @@ function parse(
105
125
  function elem(
106
126
  INSECURE_URI: string,
107
127
  content: readonly (string | HTMLElement)[],
108
- uri: ReadonlyURL,
128
+ uri: ReadonlyURL | undefined,
109
129
  origin: string,
110
130
  ): HTMLAnchorElement {
111
131
  let type: string;
112
132
  let message: string;
113
- switch (uri.protocol) {
133
+ switch (uri?.protocol) {
134
+ case undefined:
135
+ type = 'argument';
136
+ message = 'Invalid URI';
137
+ break;
114
138
  case 'http:':
115
139
  case 'https:':
116
140
  assert(uri.host);
@@ -165,11 +189,14 @@ function elem(
165
189
  message = 'Invalid content';
166
190
  }
167
191
  break;
192
+ default:
193
+ type = 'argument';
194
+ message = 'Invalid protocol';
168
195
  }
169
196
  return html('a',
170
197
  {
171
198
  class: 'invalid',
172
- ...invalid('link', type ??= 'argument', message ??= 'Invalid protocol'),
199
+ ...invalid('link', type, message),
173
200
  },
174
201
  content.length === 0
175
202
  ? INSECURE_URI
@@ -7,13 +7,12 @@ describe('Unit: parser/inline/media', () => {
7
7
  const parser = (source: string) => some(media)({ source, context: {} });
8
8
 
9
9
  it('xss', () => {
10
- assert.deepStrictEqual(inspect(parser('![]{javascript:alert}')), [['<img class="invalid" data-src="javascript:alert" alt="">'], '']);
11
- assert.deepStrictEqual(inspect(parser('![]{vbscript:alert}')), [['<img class="invalid" data-src="vbscript:alert" alt="">'], '']);
12
- assert.deepStrictEqual(inspect(parser('![]{data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K}')), [['<img class="invalid" data-src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K" alt="">'], '']);
13
- assert.deepStrictEqual(inspect(parser('![]{any:alert}')), [['<img class="invalid" data-src="any:alert" alt="">'], '']);
14
- assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt=""></a>'], '']);
15
- assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
16
- assert.deepStrictEqual(inspect(parser('![\\"]{/}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="&quot;"></a>'], '']);
10
+ assert.deepStrictEqual(inspect(parser('![]{javascript:alert}')), [['<img class="invalid" alt="javascript:alert">'], '']);
11
+ assert.deepStrictEqual(inspect(parser('![]{vbscript:alert}')), [['<img class="invalid" alt="vbscript:alert">'], '']);
12
+ assert.deepStrictEqual(inspect(parser('![]{data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K}')), [['<img class="invalid" alt="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K">'], '']);
13
+ assert.deepStrictEqual(inspect(parser('![]{any:alert}')), [['<img class="invalid" alt="any:alert">'], '']);
14
+ assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt="&quot;"></a>'], '']);
15
+ assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt="\\"></a>'], '']);
17
16
  assert.deepStrictEqual(inspect(parser('![\\"]{/}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="&quot;"></a>'], '']);
18
17
  });
19
18
 
@@ -44,41 +43,41 @@ describe('Unit: parser/inline/media', () => {
44
43
  assert.deepStrictEqual(inspect(parser('![\\ ]{b}')), undefined);
45
44
  assert.deepStrictEqual(inspect(parser('![\\\n]{b}')), undefined);
46
45
  assert.deepStrictEqual(inspect(parser('![&Tab;]{b}')), undefined);
47
- assert.deepStrictEqual(inspect(parser('![&a;]{b}')), [['<img class="invalid" data-src="b" alt="&amp;a;">'], '']);
46
+ assert.deepStrictEqual(inspect(parser('![&a;]{b}')), [['<img class="invalid" alt="&amp;a;">'], '']);
48
47
  assert.deepStrictEqual(inspect(parser('![[]{b}')), undefined);
49
48
  assert.deepStrictEqual(inspect(parser('![]]{b}')), undefined);
50
49
  assert.deepStrictEqual(inspect(parser('![a]{}')), undefined);
51
50
  assert.deepStrictEqual(inspect(parser('![a\nb]{b}')), undefined);
52
51
  assert.deepStrictEqual(inspect(parser('![a\\\nb]{b}')), undefined);
53
- assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="invalid" data-src="ttp://host" alt="">'], '']);
54
- assert.deepStrictEqual(inspect(parser('![]{tel:1234567890}')), [['<img class="invalid" data-src="tel:1234567890" alt="">'], '']);
55
- //assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0%1]}')), [['<img class="invalid" alt="">'], '']);
56
- //assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0/96]}')), [['<img class="invalid" alt="">'], '']);
57
- assert.deepStrictEqual(inspect(parser('![]{.}')), [['<img class="invalid" data-src="." alt="">'], '']);
58
- assert.deepStrictEqual(inspect(parser('![]{..}')), [['<img class="invalid" data-src=".." alt="">'], '']);
59
- assert.deepStrictEqual(inspect(parser('![]{../}')), [['<img class="invalid" data-src="../" alt="">'], '']);
60
- assert.deepStrictEqual(inspect(parser('![]{/../b}')), [['<img class="invalid" data-src="/../b" alt="">'], '']);
52
+ assert.deepStrictEqual(inspect(parser('![]{ttp://host}')), [['<img class="invalid" alt="ttp://host">'], '']);
53
+ assert.deepStrictEqual(inspect(parser('![]{tel:1234567890}')), [['<img class="invalid" alt="tel:1234567890">'], '']);
54
+ assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0%1]}')), [['<img class="invalid" alt="http://[::ffff:0:0%1]">'], '']);
55
+ assert.deepStrictEqual(inspect(parser('![]{http://[::ffff:0:0/96]}')), [['<img class="invalid" alt="http://[::ffff:0:0/96]">'], '']);
56
+ assert.deepStrictEqual(inspect(parser('![]{.}')), [['<img class="invalid" alt=".">'], '']);
57
+ assert.deepStrictEqual(inspect(parser('![]{..}')), [['<img class="invalid" alt="..">'], '']);
58
+ assert.deepStrictEqual(inspect(parser('![]{../}')), [['<img class="invalid" alt="../">'], '']);
59
+ assert.deepStrictEqual(inspect(parser('![]{/../b}')), [['<img class="invalid" alt="/../b">'], '']);
61
60
  assert.deepStrictEqual(inspect(parser(' ![]{b}')), undefined);
62
61
  assert.deepStrictEqual(inspect(parser('[]{/}')), undefined);
63
62
  });
64
63
 
65
64
  it('basic', () => {
66
- assert.deepStrictEqual(inspect(parser('![]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
67
- assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
68
- assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
69
- assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
70
- assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
71
- assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
72
- assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
73
- assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
74
- assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt=""></a>'], '']);
75
- assert.deepStrictEqual(inspect(parser('![]{"}"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt=""></a>'], '"}']);
76
- assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
77
- assert.deepStrictEqual(inspect(parser('![]{\\ }')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt=""></a>'], '']);
78
- assert.deepStrictEqual(inspect(parser('![]{\\b}')), [['<a href="\\b" target="_blank"><img class="media" data-src="\\b" alt=""></a>'], '']);
79
- assert.deepStrictEqual(inspect(parser('![]{?/../}')), [[`<a href="?/../" target="_blank"><img class="media" data-src="?/../" alt=""></a>`], '']);
80
- assert.deepStrictEqual(inspect(parser('![]{#/../}')), [[`<a href="#/../" target="_blank"><img class="media" data-src="#/../" alt=""></a>`], '']);
81
- assert.deepStrictEqual(inspect(parser('![]{^/b}')), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt=""></a>`], '']);
65
+ assert.deepStrictEqual(inspect(parser('![]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
66
+ assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
67
+ assert.deepStrictEqual(inspect(parser('![]{b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
68
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
69
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
70
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
71
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
72
+ assert.deepStrictEqual(inspect(parser('![]{ b }')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
73
+ assert.deepStrictEqual(inspect(parser('![]{"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt="&quot;"></a>'], '']);
74
+ assert.deepStrictEqual(inspect(parser('![]{"}"}')), [['<a href="&quot;" target="_blank"><img class="media" data-src="&quot;" alt="&quot;"></a>'], '"}']);
75
+ assert.deepStrictEqual(inspect(parser('![]{\\}')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt="\\"></a>'], '']);
76
+ assert.deepStrictEqual(inspect(parser('![]{\\ }')), [['<a href="\\" target="_blank"><img class="media" data-src="\\" alt="\\"></a>'], '']);
77
+ assert.deepStrictEqual(inspect(parser('![]{\\b}')), [['<a href="\\b" target="_blank"><img class="media" data-src="\\b" alt="\\b"></a>'], '']);
78
+ assert.deepStrictEqual(inspect(parser('![]{?/../}')), [[`<a href="?/../" target="_blank"><img class="media" data-src="?/../" alt="?/../"></a>`], '']);
79
+ assert.deepStrictEqual(inspect(parser('![]{#/../}')), [[`<a href="#/../" target="_blank"><img class="media" data-src="#/../" alt="#/../"></a>`], '']);
80
+ assert.deepStrictEqual(inspect(parser('![]{^/b}')), [[`<a href="/b" target="_blank"><img class="media" data-src="/b" alt="^/b"></a>`], '']);
82
81
  assert.deepStrictEqual(inspect(parser('![ a]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
83
82
  assert.deepStrictEqual(inspect(parser('![ a ]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
84
83
  assert.deepStrictEqual(inspect(parser('![a ]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="a"></a>'], '']);
@@ -86,9 +85,9 @@ describe('Unit: parser/inline/media', () => {
86
85
  assert.deepStrictEqual(inspect(parser('![a b]{c}')), [['<a href="c" target="_blank"><img class="media" data-src="c" alt="a b"></a>'], '']);
87
86
  assert.deepStrictEqual(inspect(parser('![&copy;]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="©"></a>'], '']);
88
87
  assert.deepStrictEqual(inspect(parser('![&amp;copy;]{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="&amp;copy;"></a>'], '']);
89
- assert.deepStrictEqual(inspect(parser('!{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt=""></a>'], '']);
90
- assert.deepStrictEqual(inspect(parser('!{ ][ }')), [['<a href="][" target="_blank"><img class="media" data-src="][" alt=""></a>'], '']);
91
- assert.deepStrictEqual(inspect(parser('!{ }{ }')), [['<a href="}{" target="_blank"><img class="media" data-src="}{" alt=""></a>'], '']);
88
+ assert.deepStrictEqual(inspect(parser('!{b}')), [['<a href="b" target="_blank"><img class="media" data-src="b" alt="b"></a>'], '']);
89
+ assert.deepStrictEqual(inspect(parser('!{ ][ }')), [['<a href="][" target="_blank"><img class="media" data-src="][" alt="]["></a>'], '']);
90
+ assert.deepStrictEqual(inspect(parser('!{ }{ }')), [['<a href="}{" target="_blank"><img class="media" data-src="}{" alt="}{"></a>'], '']);
92
91
  });
93
92
 
94
93
  it('nest', () => {
@@ -99,20 +98,20 @@ describe('Unit: parser/inline/media', () => {
99
98
  });
100
99
 
101
100
  it('external', () => {
102
- assert.deepStrictEqual(inspect(parser('![]{//host}')), [['<a href="//host" target="_blank"><img class="media" data-src="//host" alt=""></a>'], '']);
103
- assert.deepStrictEqual(inspect(parser('![]{//[::]}')), [['<a href="//[::]" target="_blank"><img class="media" data-src="//[::]" alt=""></a>'], '']);
101
+ assert.deepStrictEqual(inspect(parser('![]{//host}')), [['<a href="//host" target="_blank"><img class="media" data-src="//host" alt="//host"></a>'], '']);
102
+ assert.deepStrictEqual(inspect(parser('![]{//[::]}')), [['<a href="//[::]" target="_blank"><img class="media" data-src="//[::]" alt="//[::]"></a>'], '']);
104
103
  });
105
104
 
106
105
  it('attribute', () => {
107
- assert.deepStrictEqual(inspect(parser('![]{/ __proto__}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt=""></a>'], '']);
108
- assert.deepStrictEqual(inspect(parser('![]{/ constructor}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt=""></a>'], '']);
109
- assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt=""></a>'], '']);
110
- assert.deepStrictEqual(inspect(parser('![]{/ nofollow}')), [['<a href="/" rel="nofollow" target="_blank"><img class="media" data-src="/" alt=""></a>'], '']);
111
- assert.deepStrictEqual(inspect(parser('![]{/ width="4" height="3"}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="" width="4" height="3"></a>'], '']);
112
- assert.deepStrictEqual(inspect(parser('![]{/ 4x3}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="" width="4" height="3"></a>'], '']);
113
- assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio="4/3"}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
114
- assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio="4/3" nofollow}')), [['<a href="/" rel="nofollow" target="_blank"><img class="media" data-src="/" alt="" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
115
- assert.deepStrictEqual(inspect(parser('![]{/ 4:3}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
106
+ assert.deepStrictEqual(inspect(parser('![]{/ __proto__}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt="/"></a>'], '']);
107
+ assert.deepStrictEqual(inspect(parser('![]{/ constructor}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt="/"></a>'], '']);
108
+ assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio}')), [['<a href="/" target="_blank"><img class="media invalid" data-src="/" alt="/"></a>'], '']);
109
+ assert.deepStrictEqual(inspect(parser('![]{/ nofollow}')), [['<a href="/" rel="nofollow" target="_blank"><img class="media" data-src="/" alt="/"></a>'], '']);
110
+ assert.deepStrictEqual(inspect(parser('![]{/ width="4" height="3"}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="/" width="4" height="3"></a>'], '']);
111
+ assert.deepStrictEqual(inspect(parser('![]{/ 4x3}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="/" width="4" height="3"></a>'], '']);
112
+ assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio="4/3"}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="/" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
113
+ assert.deepStrictEqual(inspect(parser('![]{/ aspect-ratio="4/3" nofollow}')), [['<a href="/" rel="nofollow" target="_blank"><img class="media" data-src="/" alt="/" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
114
+ assert.deepStrictEqual(inspect(parser('![]{/ 4:3}')), [['<a href="/" target="_blank"><img class="media" data-src="/" alt="/" aspect-ratio="4/3" style="aspect-ratio: 4 / 3;"></a>'], '']);
116
115
  });
117
116
 
118
117
  });