securemark 0.294.4 → 0.294.5

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 (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/index.js +140 -89
  3. package/package.json +3 -3
  4. package/src/combinator/control/manipulation/fence.ts +2 -0
  5. package/src/combinator/control/manipulation/indent.ts +1 -1
  6. package/src/combinator/control/manipulation/match.ts +11 -8
  7. package/src/parser/api/parse.test.ts +3 -3
  8. package/src/parser/block/blockquote.test.ts +3 -9
  9. package/src/parser/block/blockquote.ts +4 -4
  10. package/src/parser/block/dlist.ts +4 -4
  11. package/src/parser/block/extension/example.ts +1 -3
  12. package/src/parser/block/extension/fig.test.ts +0 -1
  13. package/src/parser/block/extension/fig.ts +6 -6
  14. package/src/parser/block/extension/figbase.ts +1 -1
  15. package/src/parser/block/extension/figure.test.ts +1 -1
  16. package/src/parser/block/extension/figure.ts +6 -6
  17. package/src/parser/block/extension/message.ts +1 -1
  18. package/src/parser/block/extension/table.ts +4 -4
  19. package/src/parser/block/heading.ts +4 -4
  20. package/src/parser/block/reply/cite.ts +1 -1
  21. package/src/parser/block/reply/quote.ts +2 -2
  22. package/src/parser/block/sidefence.test.ts +1 -3
  23. package/src/parser/block/sidefence.ts +4 -4
  24. package/src/parser/block/table.ts +2 -2
  25. package/src/parser/block.ts +1 -1
  26. package/src/parser/header.ts +3 -3
  27. package/src/parser/inline/autolink/account.ts +5 -7
  28. package/src/parser/inline/autolink/channel.ts +15 -15
  29. package/src/parser/inline/autolink/hashtag.ts +10 -8
  30. package/src/parser/inline/code.ts +12 -18
  31. package/src/parser/inline/deletion.ts +3 -3
  32. package/src/parser/inline/emstrong.ts +3 -3
  33. package/src/parser/inline/extension/indexer.ts +1 -1
  34. package/src/parser/inline/html.ts +1 -1
  35. package/src/parser/inline/insertion.ts +3 -3
  36. package/src/parser/inline/italic.ts +3 -3
  37. package/src/parser/inline/link.ts +3 -3
  38. package/src/parser/inline/mark.ts +3 -3
  39. package/src/parser/inline/remark.ts +3 -3
  40. package/src/parser/inline.ts +2 -0
  41. package/src/parser/source/text.ts +8 -4
  42. package/src/parser/util.ts +1 -1
  43. package/src/parser/visibility.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.294.4",
3
+ "version": "0.294.5",
4
4
  "description": "Secure markdown renderer working on browsers for user input data.",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/falsandtru/securemark",
@@ -28,7 +28,7 @@
28
28
  "LICENSE"
29
29
  ],
30
30
  "dependencies": {
31
- "spica": "0.0.809"
31
+ "spica": "0.0.810"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/dompurify": "3.0.5",
@@ -42,7 +42,7 @@
42
42
  "babel-plugin-unassert": "^3.2.0",
43
43
  "concurrently": "^8.2.2",
44
44
  "eslint": "^9.8.0",
45
- "eslint-plugin-redos": "^4.4.5",
45
+ "eslint-plugin-redos": "^4.5.0",
46
46
  "eslint-webpack-plugin": "^4.2.0",
47
47
  "glob": "^11.0.0",
48
48
  "karma": "^6.4.4",
@@ -1,4 +1,5 @@
1
1
  import { Parser, List, Data, Ctx, failsafe } from '../../data/parser';
2
+ import { consume } from '../../../combinator';
2
3
  import { firstline, isBlank } from '../constraint/line';
3
4
  import { push } from 'spica/array';
4
5
 
@@ -12,6 +13,7 @@ export function fence<C extends Ctx, D extends Parser<unknown, C>[]>(opener: Reg
12
13
  const matches = opener.exec(source);
13
14
  if (!matches) return;
14
15
  assert(matches[0] === firstline(source, position));
16
+ consume(matches[0].length, context);
15
17
  const delim = matches[1];
16
18
  assert(delim && delim === delim.trim());
17
19
  if (matches[0].includes(delim, delim.length)) return;
@@ -26,7 +26,7 @@ export function indent<N>(opener: RegExp | Parser<N>, parser: Parser<N> | boolea
26
26
  context.position = source.length;
27
27
  return new List([new Data(source.slice(position))]);
28
28
  }))),
29
- ([indent]) => indent.length <= 16 ? indent.length * 2 + +(indent[0] === ' ') : -1, [])), separation),
29
+ ([indent]) => indent.length * 2 + -(indent[0] === ' '), [], 2 ** 4 - 1)), separation),
30
30
  (lines, context) => {
31
31
  assert(parser = parser as Parser<N>);
32
32
  return parser(subinput(trimBlockEnd(lines.foldl((acc, node) => acc + node.value, '')), context));
@@ -1,20 +1,23 @@
1
1
  import { Parser, failsafe } from '../../data/parser';
2
2
  import { consume } from '../../../combinator';
3
3
 
4
- export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P, cost?: boolean): P;
5
- export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>, cost = false): Parser<N> {
4
+ export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
5
+ export function match<N>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<N>): Parser<N> {
6
6
  assert(!pattern.flags.match(/[gm]/) && pattern.sticky && !pattern.source.startsWith('^'));
7
+ const count = typeof pattern === 'object'
8
+ ? /[^^\\*+][*+]/.test(pattern.source)
9
+ : false;
7
10
  return failsafe(input => {
8
11
  const { context } = input;
9
12
  const { source, position } = context;
10
13
  if (position === source.length) return;
11
14
  pattern.lastIndex = position;
12
- const param = pattern.exec(source);
13
- if (!param) return;
14
- assert(source.startsWith(param[0], position));
15
- cost && consume(param[0].length, context);
16
- const result = f(param)(input);
17
- context.position += result && context.position === position ? param[0].length : 0;
15
+ const params = pattern.exec(source);
16
+ if (!params) return;
17
+ assert(source.startsWith(params[0], position));
18
+ count && consume(params[0].length, context);
19
+ const result = f(params)(input);
20
+ context.position += result && context.position === position ? params[0].length : 0;
18
21
  assert(context.position > position || !result);
19
22
  return context.position > position
20
23
  ? result
@@ -361,9 +361,9 @@ describe('Unit: parser/api/parse', () => {
361
361
 
362
362
  it('backtrack', function () {
363
363
  this.timeout(5000);
364
- // 最悪計算量での実行速度はCommonMarkの公式JS実装の32nに対して3倍遅い程度。
364
+ // 最悪計算量での実行速度はCommonMarkの公式JS実装の32nより速い。
365
365
  // 5n = annotation/reference + link + url/math + ruby + text
366
- const source = `${'.'.repeat(0 + 0)}((([[[[#$[${'&'.repeat(19998)}`;
366
+ const source = `((([[[[#$[${'.'.repeat(19997)}`;
367
367
  assert.deepStrictEqual(
368
368
  [...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
369
369
  .map(el => el.tagName),
@@ -372,7 +372,7 @@ describe('Unit: parser/api/parse', () => {
372
372
 
373
373
  it('backtrack error', function () {
374
374
  this.timeout(5000);
375
- const source = `${'.'.repeat(0 + 1)}((([[[[#$[${'&'.repeat(19998)}`;
375
+ const source = `((([[[[#$[${'.'.repeat(19997 + 1)}`;
376
376
  assert.deepStrictEqual(
377
377
  [...parse(source, {}, { resources: { clock: 100000, recursions: [100] } }).children]
378
378
  .map(el => el.tagName),
@@ -14,6 +14,9 @@ describe('Unit: parser/block/blockquote', () => {
14
14
  assert.deepStrictEqual(inspect(parser('>'), ctx), undefined);
15
15
  assert.deepStrictEqual(inspect(parser('>a'), ctx), undefined);
16
16
  assert.deepStrictEqual(inspect(parser('>\n'), ctx), undefined);
17
+ assert.deepStrictEqual(inspect(parser('>\na'), ctx), undefined);
18
+ assert.deepStrictEqual(inspect(parser('!>\n'), ctx), undefined);
19
+ assert.deepStrictEqual(inspect(parser('!>\na'), ctx), undefined);
17
20
  assert.deepStrictEqual(inspect(parser(' > '), ctx), undefined);
18
21
  assert.deepStrictEqual(inspect(parser('>>'), ctx), undefined);
19
22
  });
@@ -38,10 +41,6 @@ describe('Unit: parser/block/blockquote', () => {
38
41
  assert.deepStrictEqual(inspect(parser('> a\\\nb'), ctx), [['<blockquote><pre>a\\<br>b</pre></blockquote>'], '']);
39
42
  assert.deepStrictEqual(inspect(parser('> a '), ctx), [['<blockquote><pre> a </pre></blockquote>'], '']);
40
43
  assert.deepStrictEqual(inspect(parser('> \na'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
41
- assert.deepStrictEqual(inspect(parser('>\na'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
42
- assert.deepStrictEqual(inspect(parser('>\n a'), ctx), [['<blockquote><pre><br> a</pre></blockquote>'], '']);
43
- assert.deepStrictEqual(inspect(parser('>\n>'), ctx), [['<blockquote><pre><br></pre></blockquote>'], '']);
44
- assert.deepStrictEqual(inspect(parser('>\n> a'), ctx), [['<blockquote><pre><br>a</pre></blockquote>'], '']);
45
44
  assert.deepStrictEqual(inspect(parser('> http://host'), ctx), [['<blockquote><pre><a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
46
45
  assert.deepStrictEqual(inspect(parser('> http://host)'), ctx), [['<blockquote><pre><a class="url" href="http://host)" target="_blank">http://host)</a></pre></blockquote>'], '']);
47
46
  assert.deepStrictEqual(inspect(parser('> !http://host'), ctx), [['<blockquote><pre>!<a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
@@ -92,11 +91,6 @@ describe('Unit: parser/block/blockquote', () => {
92
91
  assert.deepStrictEqual(inspect(parser('!>> > a\n> b'), ctx), [['<blockquote><blockquote><section><blockquote><pre>a</pre></blockquote><h2>References</h2><ol class="references"></ol></section></blockquote><section><p>b</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
93
92
  assert.deepStrictEqual(inspect(parser('!> !> a'), ctx), [['<blockquote><section><blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
94
93
  assert.deepStrictEqual(inspect(parser('!> \na'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
95
- assert.deepStrictEqual(inspect(parser('!>\n'), ctx), undefined);
96
- assert.deepStrictEqual(inspect(parser('!>\na'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
97
- assert.deepStrictEqual(inspect(parser('!>\n a'), ctx), [['<blockquote><section><p> a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
98
- assert.deepStrictEqual(inspect(parser('!>\n>'), ctx), [['<blockquote><section><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
99
- assert.deepStrictEqual(inspect(parser('!>\n> a'), ctx), [['<blockquote><section><p>a</p><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
100
94
  assert.deepStrictEqual(inspect(parser('!>> ## a\n> ## a'), ctx), [['<blockquote><blockquote><section><h2>a</h2><h2>References</h2><ol class="references"></ol></section></blockquote><section><h2>a</h2><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
101
95
  assert.deepStrictEqual(inspect(parser('!>> ~ a\n> ~ a'), ctx), [['<blockquote><blockquote><section><dl><dt>a</dt><dd></dd></dl><h2>References</h2><ol class="references"></ol></section></blockquote><section><dl><dt>a</dt><dd></dd></dl><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
102
96
  assert.deepStrictEqual(inspect(parser('!>> ~~~figure $test-a\n>> > \n>>\n~~~\n> ~~~figure $test-a\n> > \n>\n[#a]\n~~~'), ctx), [['<blockquote><blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure><h2>References</h2><ol class="references"></ol></section></blockquote><section><figure data-type="quote" data-label="test-a" data-group="test" data-number="1"><figcaption><span class="figindex">Test 1. </span><span class="figtext"><a class="index">a</a></span></figcaption><div><blockquote></blockquote></div></figure><h2>References</h2><ol class="references"></ol></section></blockquote>'], '']);
@@ -9,7 +9,7 @@ import { parse } from '../api/parse';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const segment: BlockquoteParser.SegmentParser = block(union([
12
- validate(/!?>+(?=[^\S\n]|\n[^\S\n]*\S)/y, some(contentline)),
12
+ validate(/!?>+ /y, some(contentline)),
13
13
  ]));
14
14
 
15
15
  export const blockquote: BlockquoteParser = lazy(() => block(rewrite(segment, union([
@@ -17,9 +17,9 @@ export const blockquote: BlockquoteParser = lazy(() => block(rewrite(segment, un
17
17
  open(/!(?=>)/y, markdown),
18
18
  ]))));
19
19
 
20
- const opener = /(?=>>+(?:$|\s))/y;
21
- const indent = block(open(opener, some(contentline, />(?:$|\s)/y)), false);
22
- const unindent = (source: string) => source.replace(/(?<=^|\n)>(?:[^\S\n]|(?=>*(?:$|\s)))|\n$/g, '');
20
+ const opener = /(?=>>+(?:$|[ \n]))/y;
21
+ const indent = block(open(opener, some(contentline, />(?:$|[ \n])/y)), false);
22
+ const unindent = (source: string) => source.replace(/(?<=^|\n)>(?: |(?=>*(?:$|[ \n])))|\n$/g, '');
23
23
 
24
24
  const source: BlockquoteParser.SourceParser = lazy(() => fmap(
25
25
  some(recursion(Recursion.blockquote, union([
@@ -9,7 +9,7 @@ import { unwrap } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
11
  export const dlist: DListParser = lazy(() => block(fmap(validate(
12
- /~[^\S\n]+(?=\S)/y,
12
+ /~ +(?=\S)/y,
13
13
  some(inits([
14
14
  state(State.annotation | State.reference | State.index | State.label | State.link,
15
15
  some(term)),
@@ -18,15 +18,15 @@ export const dlist: DListParser = lazy(() => block(fmap(validate(
18
18
  ns => new List([new Data(html('dl', unwrap(fillTrailingDescription(ns))))]))));
19
19
 
20
20
  const term: DListParser.TermParser = line(indexee(fmap(open(
21
- /~[^\S\n]+(?=\S)/y,
21
+ /~ +(?=\S)/y,
22
22
  visualize(trimBlank(some(union([indexer, inline])))),
23
23
  true),
24
24
  ns => new List([new Data(html('dt', { 'data-index': dataindex(ns) }, defrag(unwrap(ns))))]))));
25
25
 
26
26
  const desc: DListParser.DescriptionParser = block(fmap(open(
27
- /:[^\S\n]+(?=\S)|/y,
27
+ /: +(?=\S)|/y,
28
28
  rewrite(
29
- some(anyline, /[~:][^\S\n]+\S/y),
29
+ some(anyline, /[~:] +(?=\S)/y),
30
30
  visualize(trimBlankEnd(some(union([inline]))))),
31
31
  true),
32
32
  ns => new List([new Data(html('dd', defrag(unwrap(ns))))])),
@@ -7,10 +7,8 @@ import { unwrap, invalid } from '../../util';
7
7
  import { parse } from '../../api/parse';
8
8
  import { html } from 'typed-dom/dom';
9
9
 
10
- const opener = /(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/y;
11
-
12
10
  export const example: ExtensionParser.ExampleParser = recursion(Recursion.block, block(fmap(
13
- fence(opener, 300),
11
+ fence(/(~{3,})(?:example\/(\S+))?(?!\S)([^\n]*)(?:$|\n)/y, 300),
14
12
  // Bug: Type mismatch between outer and inner.
15
13
  (nodes: List<Data<string>>, context) => {
16
14
  const [body, overflow, closer, opener, delim, type = 'markdown', param] = unwrap(nodes);
@@ -40,7 +40,6 @@ describe('Unit: parser/block/extension/fig', () => {
40
40
  assert.deepStrictEqual(inspect(parser('[$group-name]\n~~~table\n~~~\n'), ctx), [['<figure data-type="table" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><table></table></div></figure>'], '']);
41
41
  assert.deepStrictEqual(inspect(parser('[$group-name]\n> '), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
42
42
  assert.deepStrictEqual(inspect(parser('[$group-name]\n> \n'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote></blockquote></div></figure>'], '']);
43
- assert.deepStrictEqual(inspect(parser('[$group-name]\n>\n~~~'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><pre><br>~~~</pre></blockquote></div></figure>'], '']);
44
43
  assert.deepStrictEqual(inspect(parser('[$group-name]\n!> *a*'), ctx), [['<figure data-type="quote" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><blockquote><section><p><em>a</em></p><h2>References</h2><ol class="references"></ol></section></blockquote></div></figure>'], '']);
45
44
  assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}'), ctx), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
46
45
  assert.deepStrictEqual(inspect(parser('[$group-name]\n![]{https://host}\n'), ctx), [['<figure data-type="media" data-label="group-name" data-group="group"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
@@ -1,5 +1,5 @@
1
1
  import { ExtensionParser } from '../../block';
2
- import { union, sequence, some, block, line, validate, verify, rewrite, close, convert } from '../../../combinator';
2
+ import { union, sequence, some, block, line, verify, rewrite, close, convert } from '../../../combinator';
3
3
  import { contentline } from '../../source';
4
4
  import { figure } from './figure';
5
5
  import { segment as seg_label } from '../../inline/extension/label';
@@ -12,9 +12,9 @@ import { media, lineshortmedia } from '../../inline';
12
12
 
13
13
  import FigParser = ExtensionParser.FigParser;
14
14
 
15
- export const segment: FigParser.SegmentParser = block(validate(/\[?\$/y,
15
+ export const segment: FigParser.SegmentParser = block(
16
16
  sequence([
17
- line(close(seg_label, /(?=\s).*\n/y)),
17
+ line(close(seg_label, /(?!\S).*\n/y)),
18
18
  union([
19
19
  seg_code,
20
20
  seg_math,
@@ -23,12 +23,12 @@ export const segment: FigParser.SegmentParser = block(validate(/\[?\$/y,
23
23
  seg_placeholder,
24
24
  some(contentline),
25
25
  ]),
26
- ])));
26
+ ]));
27
27
 
28
28
  export const fig: FigParser = block(rewrite(segment, verify(convert(
29
29
  (source, context) => {
30
30
  // Bug: TypeScript
31
- const fence = (/^[^\n]*\n!?>+\s/.test(source) && source.match(/^~{3,}(?=[^\S\n]*$)/mg) as string[] || [])
31
+ const fence = (/^[^\n]*\n!?>+ /.test(source) && source.match(/^~{3,}(?=[^\S\n]*$)/mg) as string[] || [])
32
32
  .reduce((max, fence) => fence > max ? fence : max, '~~') + '~';
33
33
  return parser({ context })
34
34
  ? `${fence}figure ${source.replace(/^(.+\n.+\n)([\S\s]+?)\n?$/, '$1\n$2')}\n${fence}`
@@ -39,7 +39,7 @@ export const fig: FigParser = block(rewrite(segment, verify(convert(
39
39
  ([{ value: el }]) => el.tagName === 'FIGURE')));
40
40
 
41
41
  const parser = sequence([
42
- line(close(seg_label, /(?=\s).*\n/y)),
42
+ line(close(seg_label, /(?!\S).*\n/y)),
43
43
  line(union([
44
44
  media,
45
45
  lineshortmedia,
@@ -5,7 +5,7 @@ import { label } from '../../inline/extension/label';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
7
  export const figbase: ExtensionParser.FigbaseParser = block(fmap(
8
- validate(/\[?\$-(?:[0-9]+\.)*0\]?[^\S\n]*(?!\S|\n[^\S\n]*\S)/y,
8
+ validate(/\[?\$-(?:[0-9]+\.)*0\]?(?:$|[ \n])/y,
9
9
  line(union([label]))),
10
10
  ([{ value: el }]) => {
11
11
  const label = el.getAttribute('data-label')!;
@@ -71,7 +71,7 @@ describe('Unit: parser/block/extension/figure', () => {
71
71
  assert.deepStrictEqual(inspect(parser('~~~figure [$-0.0]\n$$\n\n$$\n~~~'), ctx), [['<figure data-type="math" data-label="$-0.0" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><div class="math" translate="no">$$\n\n$$</div></div></figure>'], '']);
72
72
  assert.deepStrictEqual(inspect(parser('~~~figure [$-name]\n!https://host\n~~~'), ctx), [['<figure data-type="media" data-label="$-name" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext"></span></figcaption><div><a href="https://host" target="_blank"><img class="media" data-src="https://host" alt="https://host"></a></div></figure>'], '']);
73
73
  assert.deepStrictEqual(inspect(parser('~~~figure [$-name]\n$$\n\n$$\n\ncaption\n~~~'), ctx), [['<figure data-type="math" data-label="$-name" data-group="$" class="invalid"><figcaption><span class="figindex"></span><span class="figtext">caption</span></figcaption><div><div class="math" translate="no">$$\n\n$$</div></div></figure>'], '']);
74
- assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n${'>\n'.repeat(500)}\n~~~`), ctx, '>'), [['<figure data-type="quote" data-label="group-name" data-group="group">'], '']);
74
+ assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n${'> \n'.repeat(500)}\n~~~`), ctx, '>'), [['<figure data-type="quote" data-label="group-name" data-group="group">'], '']);
75
75
  assert.deepStrictEqual(inspect(parser(`~~~figure [$group-name]\n~~~\n0${'\n'.repeat(300)}~~~\n~~~`), ctx, '>'), [['<figure data-type="example" data-label="group-name" data-group="group">'], '']);
76
76
  });
77
77
 
@@ -21,7 +21,7 @@ import { html, defrag } from 'typed-dom/dom';
21
21
  import FigureParser = ExtensionParser.FigureParser;
22
22
 
23
23
  export const segment: FigureParser.SegmentParser = block(match(
24
- /(~{3,})(?:figure[^\S\n])?(?=\[?\$)/y,
24
+ /(~{3,})(?:figure )?(?=\[?\$)/y,
25
25
  memoize(
26
26
  ([, fence], closer = new RegExp(String.raw`${fence}[^\S\n]*(?:$|\n)`, 'y')) => close(
27
27
  sequence([
@@ -44,12 +44,12 @@ export const segment: FigureParser.SegmentParser = block(match(
44
44
  ]),
45
45
  ]),
46
46
  closer),
47
- ([, fence]) => fence.length <= 16 ? fence.length : -1, [])));
47
+ ([, fence]) => fence.length - 1, [], 2 ** 4 - 1)));
48
48
 
49
49
  export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
50
50
  convert(source => source.slice(source.match(/^~+(?:\w+\s+)?/)![0].length, source.trimEnd().lastIndexOf('\n')),
51
51
  sequence([
52
- line(sequence([label, str(/(?=\s).*\n/y)])),
52
+ line(sequence([label, str(/(?!\S).*\n/y)])),
53
53
  inits([
54
54
  block(union([
55
55
  ulist,
@@ -83,7 +83,7 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
83
83
  ]);
84
84
  })),
85
85
  fmap(
86
- fence(/(~{3,})(?:figure|\[?\$\S*)(?!\S)[^\n]*(?:$|\n)/y, 300),
86
+ fence(/(~{3,})(?:figure(?=$|[ \n])|\[?\$)[^\n]*(?:$|\n)/y, 300),
87
87
  (nodes, context) => {
88
88
  const [body, overflow, closer, opener, delim] = unwrap<string>(nodes);
89
89
  const violation =
@@ -95,11 +95,11 @@ export const figure: FigureParser = block(fallback(rewrite(segment, fmap(
95
95
  'fence',
96
96
  `Invalid trailing line after the closing delimiter "${delim}"`,
97
97
  ] ||
98
- !seg_label(subinput(opener.match(/^~+(?:figure[^\S\n]+)?(\[?\$\S+)/)?.[1] ?? '', context)) && [
98
+ !seg_label(subinput(opener.match(/^~+(?:figure )?(\[?\$\S+)/)?.[1] ?? '', context)) && [
99
99
  'label',
100
100
  'Invalid label',
101
101
  ] ||
102
- /^~+(?:figure[^\S\n]+)?(\[?\$\S+)[^\S\n]+\S/.test(opener) && [
102
+ /^~+(?:figure )?(\[?\$\S+)[^\S\n]+\S/.test(opener) && [
103
103
  'argument',
104
104
  'Invalid argument',
105
105
  ] ||
@@ -20,7 +20,7 @@ import { html } from 'typed-dom/dom';
20
20
  import MessageParser = ExtensionParser.MessageParser;
21
21
 
22
22
  export const message: MessageParser = block(fmap(
23
- fence(/(~{3,})message\/(\S+)([^\n]*)(?:$|\n)/y, 300),
23
+ fence(/(~{3,})message\/(\S+)(?!\S)([^\n]*)(?:$|\n)/y, 300),
24
24
  // Bug: Type mismatch between outer and inner.
25
25
  (nodes: List<Data<string>>, context) => {
26
26
  const [body, overflow, closer, opener, delim, type, param] = unwrap(nodes);
@@ -83,10 +83,10 @@ const align: AlignParser = line(fmap(
83
83
  union([str(alignment)]),
84
84
  ([{ value }]) => new List([new Data(value.split('/').map(s => s.split('')) as [string[], string[]?])])));
85
85
 
86
- const delimiter = /[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|[#:](?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y;
86
+ const delimiter = /[-=<>]+(?:\/[-=^v]*)?(?=[^\S\n]*\n)|[#:](?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y;
87
87
 
88
88
  const head: CellParser.HeadParser = block(fmap(open(
89
- str(/#(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y),
89
+ str(/#(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y),
90
90
  rewrite(
91
91
  inits([
92
92
  anyline,
@@ -108,7 +108,7 @@ const head: CellParser.HeadParser = block(fmap(open(
108
108
  false);
109
109
 
110
110
  const data: CellParser.DataParser = block(fmap(open(
111
- str(/:(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=\s)/y),
111
+ str(/:(?:(?!:\D|0)\d*:(?!0)\d*)?(?:!+[+]?)?(?=[ \n])/y),
112
112
  rewrite(
113
113
  inits([
114
114
  anyline,
@@ -133,7 +133,7 @@ const dataline: CellParser.DatalineParser = line(
133
133
  rewrite(
134
134
  contentline,
135
135
  union([
136
- validate(/!+\s/y, convert(source => `:${source}`, data, false)),
136
+ validate(/!+ /y, convert(source => `:${source}`, data, false)),
137
137
  convert(source => `: ${source}`, data, false),
138
138
  ])));
139
139
 
@@ -1,16 +1,16 @@
1
1
  import { HeadingParser } from '../block';
2
2
  import { State } from '../context';
3
3
  import { List, Data } from '../../combinator/data/parser';
4
- import { union, some, state, block, line, validate, focus, rewrite, open, fmap } from '../../combinator';
4
+ import { union, some, state, block, line, focus, rewrite, open, fmap } from '../../combinator';
5
5
  import { inline, indexee, indexer, dataindex } from '../inline';
6
6
  import { str } from '../source';
7
7
  import { visualize, trimBlank } from '../visibility';
8
8
  import { unwrap, invalid } from '../util';
9
9
  import { html, defrag } from 'typed-dom/dom';
10
10
 
11
- export const segment: HeadingParser.SegmentParser = block(validate('#', focus(
12
- /#+[^\S\n]+\S[^\n]*(?:\n#+(?!\S)[^\n]*)*(?:$|\n)/y,
13
- some(line(({ context: { source } }) => new List([new Data(source)]))))));
11
+ export const segment: HeadingParser.SegmentParser = block(focus(
12
+ /#+ +\S[^\n]*(?:\n#+(?=$|[ \n])[^\n]*)*(?:$|\n)/y,
13
+ some(line(({ context: { source } }) => new List([new Data(source)])))));
14
14
 
15
15
  export const heading: HeadingParser = block(rewrite(segment,
16
16
  // その他の表示制御は各所のCSSで行う。
@@ -17,7 +17,7 @@ export const cite: ReplyParser.CiteParser = line(fmap(
17
17
  // リンクの実装は後で検討
18
18
  focus(/>>#\S*(?=\s*$)/y, ({ context: { source } }) => new List([new Data(html('a', { class: 'anchor' }, source))])),
19
19
  focus(/>>https?:\/\/\S+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(html('a', { class: 'anchor', href: source.slice(2).trimEnd(), target: '_blank' }, source))])),
20
- focus(/>>.+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(source)])),
20
+ focus(/>>\S+(?=\s*$)/y, ({ context: { source } }) => new List([new Data(source)])),
21
21
  ])),
22
22
  nodes => {
23
23
  const quotes = nodes.head!.value as string;
@@ -7,14 +7,14 @@ import { linebreak, unescsource, anyline } from '../../source';
7
7
  import { unwrap } from '../../util';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
- export const syntax = />+[^\S\n]/y;
10
+ export const syntax = />+ /y;
11
11
 
12
12
  export const quote: ReplyParser.QuoteParser = lazy(() => block(fmap(
13
13
  rewrite(
14
14
  some(validate(syntax, anyline)),
15
15
  convert(
16
16
  // TODO: インデント数を渡してインデント数前の行頭確認を行う実装に置き換える
17
- source => source.replace(/(?<=^>+[^\S\n])/mg, '\r'),
17
+ source => source.replace(/(?<=^>+ )/mg, '\r'),
18
18
  some(union([
19
19
  // quote補助関数が残した数式をパースする。
20
20
  math,
@@ -39,9 +39,7 @@ describe('Unit: parser/block/sidefence', () => {
39
39
  assert.deepStrictEqual(inspect(parser('| a '), ctx), [['<blockquote class="invalid"><pre> a </pre></blockquote>'], '']);
40
40
  assert.deepStrictEqual(inspect(parser('| \na'), ctx), undefined);
41
41
  assert.deepStrictEqual(inspect(parser('|\na'), ctx), undefined);
42
- assert.deepStrictEqual(inspect(parser('|\n a'), ctx), undefined);
43
- assert.deepStrictEqual(inspect(parser('|\n|'), ctx), [['<blockquote class="invalid"><pre><br></pre></blockquote>'], '']);
44
- assert.deepStrictEqual(inspect(parser('|\n| a'), ctx), [['<blockquote class="invalid"><pre><br>a</pre></blockquote>'], '']);
42
+ assert.deepStrictEqual(inspect(parser('|\n| a'), ctx), undefined);
45
43
  assert.deepStrictEqual(inspect(parser('| http://host'), ctx), [['<blockquote class="invalid"><pre><a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
46
44
  assert.deepStrictEqual(inspect(parser('| http://host)'), ctx), [['<blockquote class="invalid"><pre><a class="url" href="http://host)" target="_blank">http://host)</a></pre></blockquote>'], '']);
47
45
  assert.deepStrictEqual(inspect(parser('| !http://host'), ctx), [['<blockquote class="invalid"><pre>!<a class="url" href="http://host" target="_blank">http://host</a></pre></blockquote>'], '']);
@@ -8,7 +8,7 @@ import { unwrap, invalid } from '../util';
8
8
  import { html, define, defrag } from 'typed-dom/dom';
9
9
 
10
10
  export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
11
- /(?=\|+(?:[^\S\n]|\n\|))(?:\|+(?:[^\S\n][^\n]*)?(?:$|\n))+$/y,
11
+ /\|+ [^\n]*(?:\n\|+(?=$|[ \n])[^\n]*)*(?:$|\n)/y,
12
12
  union([source])),
13
13
  ([{ value }]) => new List([
14
14
  new Data(define(value, {
@@ -17,13 +17,13 @@ export const sidefence: SidefenceParser = lazy(() => block(fmap(focus(
17
17
  })),
18
18
  ]))));
19
19
 
20
- const opener = /(?=\|\|+(?:$|\s))/y;
21
- const unindent = (source: string) => source.replace(/(?<=^|\n)\|(?:[^\S\n]|(?=\|*(?:$|\s)))|\n$/g, '');
20
+ const opener = /(?=\|\|+(?:$|[ \n]))/y;
21
+ const unindent = (source: string) => source.replace(/(?<=^|\n)\|(?: |(?=\|*(?:$|[ \n])))|\n$/g, '');
22
22
 
23
23
  const source: SidefenceParser.SourceParser = lazy(() => fmap(
24
24
  some(recursion(Recursion.block, union([
25
25
  focus(
26
- /(?:\|\|+(?:[^\S\n][^\n]*)?(?:$|\n))+/y,
26
+ /(?:\|\|+(?=$|[ \n])[^\n]*(?:$|\n))+/y,
27
27
  convert(unindent, source, false, true)),
28
28
  rewrite(
29
29
  some(contentline, opener),
@@ -28,7 +28,7 @@ export const table: TableParser = lazy(() => block(fmap(validate(
28
28
  ]))));
29
29
 
30
30
  const row = <P extends CellParser | AlignParser>(parser: P, optional: boolean): RowParser<P> => fallback(fmap(
31
- line(surround(/(?=\|)/y, some(union([parser])), /[|\\]?\s*$/y, optional)),
31
+ line(surround(/(?=\|)/y, some(union([parser])), /\|?\s*$/y, optional)),
32
32
  ns => new List([new Data(html('tr', unwrap(ns)))])),
33
33
  rewrite(contentline, ({ context: { source } }) => new List([
34
34
  new Data(html('tr', {
@@ -53,7 +53,7 @@ const cell: CellParser = surround(
53
53
  close(medialink, /\s*(?=\||$)/y),
54
54
  close(media, /\s*(?=\||$)/y),
55
55
  close(shortmedia, /\s*(?=\||$)/y),
56
- trimBlank(some(inline, /\|/y, [[/[|\\]?\s*$/y, 9]])),
56
+ trimBlank(some(inline, /\|/y, [[/\|?\s*$/y, 9]])),
57
57
  ]),
58
58
  /[^|]*/y, true);
59
59
 
@@ -114,7 +114,7 @@ export const block: BlockParser = reset(
114
114
  ]) as any));
115
115
 
116
116
  function error(parser: BlockParser): BlockParser {
117
- const reg = new RegExp(String.raw`^${Command.Error}.*\n`)
117
+ const reg = new RegExp(String.raw`^${Command.Error}[^\n]*\n`)
118
118
  return recover<BlockParser>(fallback(
119
119
  open(Command.Error, ({ context: { source, position } }) => { throw new Error(source.slice(position).split('\n', 1)[0]); }),
120
120
  parser),
@@ -8,7 +8,7 @@ import { normalize } from './api/normalize';
8
8
  import { html, defrag } from 'typed-dom/dom';
9
9
 
10
10
  export const header: MarkdownParser.HeaderParser = lazy(() => validate(
11
- /---+[^\S\v\f\r\n]*\r?\n(?=\S)/y,
11
+ /---+ *\r?\n(?=\S)/y,
12
12
  inits([
13
13
  rewrite(
14
14
  ({ context }) => {
@@ -24,7 +24,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
24
24
  block(
25
25
  union([
26
26
  validate(({ context }) => context.header ?? true,
27
- focus(/(---+)[^\S\v\f\r\n]*\r?\n(?:[A-Za-z][0-9A-Za-z]*(?:-[A-Za-z][0-9A-Za-z]*)*:[ \t]+\S[^\v\f\r\n]*\r?\n){1,100}\1[^\S\v\f\r\n]*(?:$|\r?\n)/y,
27
+ focus(/(---+) *\r?\n(?:[A-Za-z][0-9A-Za-z]*(?:-[0-9A-Za-z]+)*:[ \t]+\S[^\r\n]*\r?\n){1,100}\1 *(?:$|\r?\n)/y,
28
28
  convert(source =>
29
29
  normalize(source.slice(source.indexOf('\n') + 1, source.trimEnd().lastIndexOf('\n'))).replace(/(\S)\s+$/mg, '$1'),
30
30
  fmap(
@@ -48,7 +48,7 @@ export const header: MarkdownParser.HeaderParser = lazy(() => validate(
48
48
  ]);
49
49
  },
50
50
  ]))),
51
- clear(str(/[^\S\v\f\r\n]*\r?\n/y)),
51
+ clear(str(/ *\r?\n/y)),
52
52
  ])));
53
53
 
54
54
  const field: MarkdownParser.HeaderParser.FieldParser = line(({ context: { source, position } }) => {
@@ -1,7 +1,7 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { State, Backtrack } from '../../context';
3
3
  import { List, Data } from '../../../combinator/data/parser';
4
- import { union, tails, state, constraint, rewrite, open, convert, fmap, lazy } from '../../../combinator';
4
+ import { union, state, constraint, rewrite, surround, convert, fmap, lazy } from '../../../combinator';
5
5
  import { unsafelink } from '../link';
6
6
  import { str } from '../../source';
7
7
  import { define } from 'typed-dom/dom';
@@ -9,13 +9,11 @@ import { define } from 'typed-dom/dom';
9
9
  // https://example/@user must be a user page or a redirect page going there.
10
10
 
11
11
  export const account: AutolinkParser.AccountParser = lazy(() => rewrite(
12
- open(
12
+ surround(
13
13
  /(?<![0-9a-z])@/yi,
14
- tails([
15
- str(/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi),
16
- str(/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@#]|>>|:\S)/yi),
17
- ]),
18
- false,
14
+ str(/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi),
15
+ str(/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@#]|>>|:\S)/yi),
16
+ true, undefined, undefined,
19
17
  [3 | Backtrack.autolink]),
20
18
  constraint(State.autolink, state(State.autolink, fmap(convert(
21
19
  source =>
@@ -1,7 +1,7 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { State, Backtrack } from '../../context';
3
3
  import { List, Data } from '../../../combinator/data/parser';
4
- import { union, tails, sequence, some, state, constraint, verify, rewrite, open, convert, fmap, lazy } from '../../../combinator';
4
+ import { union, sequence, some, state, constraint, verify, rewrite, surround, convert, fmap, lazy } from '../../../combinator';
5
5
  import { unsafelink } from '../link';
6
6
  import { emoji } from './hashtag';
7
7
  import { str } from '../../source';
@@ -11,23 +11,23 @@ import { define } from 'typed-dom/dom';
11
11
 
12
12
  export const channel: AutolinkParser.ChannelParser = lazy(() => rewrite(
13
13
  sequence([
14
- open(
14
+ surround(
15
15
  /(?<![0-9a-z])@/yi,
16
- tails([
17
- str(/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi),
18
- str(/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@]|>>|:\S)/yi),
19
- ]),
20
- false,
16
+ str(/[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?(?:\.[0-9a-z](?:(?:[0-9a-z]|-(?=[0-9a-z])){0,61}[0-9a-z])?)*\//yi),
17
+ str(/[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*(?![-.]?[0-9a-z@]|>>|:\S)/yi),
18
+ true, undefined, undefined,
21
19
  [3 | Backtrack.autolink]),
22
- some(open(
20
+ some(verify(surround(
23
21
  '#',
24
- verify(
25
- str(new RegExp([
26
- /(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^'\p{C}\p{S}\p{P}\s]|emoji))+(?![0-9a-z@]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source,
27
- ].join('').replace(/emoji/g, emoji), 'yu')),
28
- ([{ value }]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value)),
29
- false,
30
- [3 | Backtrack.autolink])),
22
+ str(new RegExp([
23
+ /(?!['_])(?:[^\p{C}\p{S}\p{P}\s]|emoji|'(?=[0-9A-Za-z])|_(?=[^\p{C}\p{S}\p{P}\s]|emoji))+/yu.source,
24
+ ].join('').replace(/emoji/g, emoji), 'yu')),
25
+ str(new RegExp([
26
+ /(?![0-9a-z@]|>>|:\S|[^\p{C}\p{S}\p{P}\s]|emoji)/yu.source,
27
+ ].join('').replace(/emoji/g, emoji), 'yu')),
28
+ false, undefined, undefined,
29
+ [3 | Backtrack.autolink]),
30
+ ([{ value }]) => !/^[0-9]{1,4}$|^[0-9]{5}/.test(value as string))),
31
31
  ]),
32
32
  constraint(State.autolink, state(State.autolink, fmap(convert(
33
33
  source =>