securemark 0.283.6 → 0.284.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 (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +11 -11
  3. package/dist/index.js +172 -121
  4. package/markdown.d.ts +44 -30
  5. package/package.json +1 -1
  6. package/src/combinator/control/constraint/block.ts +3 -2
  7. package/src/combinator/control/constraint/contract.ts +6 -4
  8. package/src/combinator/control/manipulation/lazy.ts +3 -1
  9. package/src/combinator/control/manipulation/match.ts +3 -2
  10. package/src/combinator/control/manipulation/scope.ts +3 -2
  11. package/src/combinator/control/monad/bind.ts +3 -2
  12. package/src/combinator/data/parser/context.ts +12 -8
  13. package/src/parser/inline/autolink/account.ts +5 -4
  14. package/src/parser/inline/autolink/anchor.ts +5 -4
  15. package/src/parser/inline/autolink/channel.ts +3 -2
  16. package/src/parser/inline/autolink/email.ts +7 -4
  17. package/src/parser/inline/autolink/hashnum.ts +5 -4
  18. package/src/parser/inline/autolink/hashtag.ts +5 -4
  19. package/src/parser/inline/autolink/url.test.ts +1 -1
  20. package/src/parser/inline/code.ts +3 -3
  21. package/src/parser/inline/extension/index.ts +2 -2
  22. package/src/parser/inline/extension/label.ts +5 -5
  23. package/src/parser/inline/extension/placeholder.ts +3 -3
  24. package/src/parser/inline/extension.ts +3 -3
  25. package/src/parser/inline/html.ts +2 -2
  26. package/src/parser/inline/italic.test.ts +53 -0
  27. package/src/parser/inline/italic.ts +21 -0
  28. package/src/parser/inline/link.ts +2 -2
  29. package/src/parser/inline/math.ts +1 -1
  30. package/src/parser/inline/remark.ts +3 -3
  31. package/src/parser/inline/ruby.ts +3 -3
  32. package/src/parser/inline/template.ts +1 -1
  33. package/src/parser/inline.test.ts +5 -3
  34. package/src/parser/inline.ts +59 -21
  35. package/src/parser/source/text.ts +1 -0
  36. package/src/parser/visibility.ts +3 -2
package/markdown.d.ts CHANGED
@@ -661,11 +661,12 @@ export namespace MarkdownParser {
661
661
  InlineParser.EmStrongParser,
662
662
  InlineParser.StrongParser,
663
663
  InlineParser.EmphasisParser,
664
+ InlineParser.ItalicParser,
664
665
  InlineParser.MathParser,
665
666
  InlineParser.CodeParser,
666
667
  InlineParser.HTMLEntityParser,
667
- InlineParser.AutolinkParser,
668
668
  InlineParser.BracketParser,
669
+ InlineParser.AutolinkParser,
669
670
  SourceParser.TextParser,
670
671
  ]> {
671
672
  }
@@ -1066,6 +1067,14 @@ export namespace MarkdownParser {
1066
1067
  ]>,
1067
1068
  ]> {
1068
1069
  }
1070
+ export interface ItalicParser extends
1071
+ // ///abc///
1072
+ Inline<'italic'>,
1073
+ Parser<HTMLElement | string, Context, [
1074
+ InlineParser,
1075
+ ItalicParser,
1076
+ ]> {
1077
+ }
1069
1078
  export interface MathParser extends
1070
1079
  // $expr$
1071
1080
  // ${expr}$
@@ -1121,6 +1130,28 @@ export namespace MarkdownParser {
1121
1130
  ]> {
1122
1131
  }
1123
1132
  }
1133
+ export interface BracketParser extends
1134
+ // ()
1135
+ // []
1136
+ // {}
1137
+ // ""
1138
+ Inline<'bracket'>,
1139
+ Parser<HTMLElement | string, Context, [
1140
+ SourceParser.StrParser,
1141
+ InlineParser,
1142
+ SourceParser.StrParser,
1143
+ InlineParser,
1144
+ InlineParser,
1145
+ InlineParser,
1146
+ InlineParser,
1147
+ InlineParser,
1148
+ InlineParser,
1149
+ InlineParser,
1150
+ InlineParser,
1151
+ InlineParser,
1152
+ InlineParser,
1153
+ ]> {
1154
+ }
1124
1155
  export interface AutolinkParser extends
1125
1156
  Inline<'autolink'>,
1126
1157
  Parser<HTMLElement | string, Context, [
@@ -1185,14 +1216,15 @@ export namespace MarkdownParser {
1185
1216
  export interface EmailParser extends
1186
1217
  // user@host
1187
1218
  Inline<'email'>,
1188
- Parser<HTMLAnchorElement, Context, [
1189
- SourceParser.StrParser,
1219
+ Parser<string | HTMLAnchorElement, Context, [
1220
+ Parser<HTMLAnchorElement, Context, []>,
1221
+ Parser<string, Context, []>,
1190
1222
  ]> {
1191
1223
  }
1192
1224
  export interface ChannelParser extends
1193
1225
  // @user#tag
1194
1226
  Inline<'channel'>,
1195
- Parser<HTMLAnchorElement, Context, [
1227
+ Parser<string | HTMLAnchorElement, Context, [
1196
1228
  InlineParser.AutolinkParser.AccountParser,
1197
1229
  InlineParser.AutolinkParser.HashtagParser,
1198
1230
  ]> {
@@ -1200,54 +1232,36 @@ export namespace MarkdownParser {
1200
1232
  export interface AccountParser extends
1201
1233
  // @user
1202
1234
  Inline<'account'>,
1203
- Parser<HTMLAnchorElement, Context, [
1235
+ Parser<string | HTMLAnchorElement, Context, [
1204
1236
  LinkParser.UnsafeLinkParser,
1237
+ Parser<string, Context, []>,
1205
1238
  ]> {
1206
1239
  }
1207
1240
  export interface HashtagParser extends
1208
1241
  // #tag
1209
1242
  Inline<'hashtag'>,
1210
- Parser<HTMLAnchorElement, Context, [
1243
+ Parser<string | HTMLAnchorElement, Context, [
1211
1244
  LinkParser.UnsafeLinkParser,
1245
+ Parser<string, Context, []>,
1212
1246
  ]> {
1213
1247
  }
1214
1248
  export interface HashnumParser extends
1215
1249
  // #1
1216
1250
  Inline<'hashnum'>,
1217
- Parser<HTMLAnchorElement, Context, [
1251
+ Parser<string | HTMLAnchorElement, Context, [
1218
1252
  LinkParser.UnsafeLinkParser,
1253
+ Parser<string, Context, []>,
1219
1254
  ]> {
1220
1255
  }
1221
1256
  export interface AnchorParser extends
1222
1257
  // >>1
1223
1258
  Inline<'anchor'>,
1224
- Parser<HTMLAnchorElement, Context, [
1259
+ Parser<string | HTMLAnchorElement, Context, [
1225
1260
  LinkParser.UnsafeLinkParser,
1261
+ Parser<string, Context, []>,
1226
1262
  ]> {
1227
1263
  }
1228
1264
  }
1229
- export interface BracketParser extends
1230
- // ()
1231
- // []
1232
- // {}
1233
- // ""
1234
- Inline<'bracket'>,
1235
- Parser<HTMLElement | string, Context, [
1236
- SourceParser.StrParser,
1237
- InlineParser,
1238
- SourceParser.StrParser,
1239
- InlineParser,
1240
- InlineParser,
1241
- InlineParser,
1242
- InlineParser,
1243
- InlineParser,
1244
- InlineParser,
1245
- InlineParser,
1246
- InlineParser,
1247
- InlineParser,
1248
- InlineParser,
1249
- ]> {
1250
- }
1251
1265
  }
1252
1266
  export interface AutolinkParser extends
1253
1267
  Markdown<'autolink'>,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securemark",
3
- "version": "0.283.6",
3
+ "version": "0.284.0",
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",
@@ -4,9 +4,10 @@ import { firstline, isBlank } from './line';
4
4
  export function block<P extends Parser<unknown>>(parser: P, separation?: boolean): P;
5
5
  export function block<T>(parser: Parser<T>, separation = true): Parser<T> {
6
6
  assert(parser);
7
- return ({ source, context }) => {
7
+ return input => {
8
+ const { source } = input;
8
9
  if (source === '') return;
9
- const result = parser({ source, context });
10
+ const result = parser(input);
10
11
  if (result === undefined) return;
11
12
  const rest = exec(result);
12
13
  if (separation && !isBlank(firstline(rest))) return;
@@ -23,10 +23,11 @@ export function validate<T>(patterns: string | RegExp | (string | RegExp)[] | ((
23
23
  ? `|| source.slice(0, ${pattern.length}) === '${pattern}'`
24
24
  : `|| /${pattern.source}/${pattern.flags}.test(source)`).join('').slice(2),
25
25
  ].join(''));
26
- return ({ source, context }) => {
26
+ return input => {
27
+ const { source } = input;
27
28
  if (source === '') return;
28
29
  if (!match(source)) return;
29
- const result = parser({ source, context });
30
+ const result = parser(input);
30
31
  assert(check(source, result));
31
32
  if (result === undefined) return;
32
33
  assert(exec(result).length < source.length);
@@ -47,9 +48,10 @@ function guard<T>(f: (input: Input<Ctx>) => boolean, parser: Parser<T>): Parser<
47
48
  export function verify<P extends Parser<unknown>>(parser: P, cond: (results: readonly Tree<P>[], rest: string, context: Context<P>) => boolean): P;
48
49
  export function verify<T>(parser: Parser<T>, cond: (results: readonly T[], rest: string, context: Ctx) => boolean): Parser<T> {
49
50
  assert(parser);
50
- return ({ source, context }) => {
51
+ return input => {
52
+ const { source, context } = input;
51
53
  if (source === '') return;
52
- const result = parser({ source, context });
54
+ const result = parser(input);
53
55
  assert(check(source, result));
54
56
  if (result === undefined) return;
55
57
  if (!cond(eval(result), exec(result), context)) return;
@@ -4,5 +4,7 @@ export function lazy<P extends Parser<unknown>>(builder: () => P): P;
4
4
  export function lazy<T>(builder: () => Parser<T>): Parser<T> {
5
5
  let parser: Parser<T>;
6
6
  return input =>
7
- (parser ??= builder())(input);
7
+ parser !== undefined
8
+ ? parser(input)
9
+ : (parser = builder())(input);
8
10
  }
@@ -3,12 +3,13 @@ import { Parser, exec, check } from '../../data/parser';
3
3
  export function match<P extends Parser<unknown>>(pattern: RegExp, f: (matched: RegExpMatchArray) => P): P;
4
4
  export function match<T>(pattern: RegExp, f: (matched: RegExpMatchArray) => Parser<T>): Parser<T> {
5
5
  assert(!pattern.flags.match(/[gmy]/) && pattern.source.startsWith('^'));
6
- return ({ source, context }) => {
6
+ return input => {
7
+ const { source } = input;
7
8
  if (source === '') return;
8
9
  const param = source.match(pattern);
9
10
  if (!param) return;
10
11
  assert(source.startsWith(param[0]));
11
- const result = f(param)({ source, context });
12
+ const result = f(param)(input);
12
13
  assert(check(source, result, false));
13
14
  if (result === undefined) return;
14
15
  return exec(result).length < source.length && exec(result).length <= source.length
@@ -32,13 +32,14 @@ export function rewrite<P extends Parser<unknown>>(scope: Parser<unknown, Contex
32
32
  export function rewrite<T>(scope: Parser<unknown>, parser: Parser<T>): Parser<T> {
33
33
  assert(scope);
34
34
  assert(parser);
35
- return ({ source, context }) => {
35
+ return input => {
36
+ const { source, context } = input;
36
37
  if (source === '') return;
37
38
  const { logger } = context;
38
39
  context.logger = {};
39
40
  //const { resources = { clock: 0 } } = context;
40
41
  //const clock = resources.clock;
41
- const res1 = scope({ source, context });
42
+ const res1 = scope(input);
42
43
  assert(check(source, res1));
43
44
  //resources.clock = clock;
44
45
  context.logger = logger;
@@ -6,9 +6,10 @@ export function bind<T, P extends Parser<unknown>>(parser: Parser<T, Context<P>,
6
6
  export function bind<U, P extends Parser<unknown>>(parser: P, f: (nodes: Tree<P>[], rest: string, context: Context<P>) => Result<U, Context<P>, SubParsers<P>>): Parser<U, Context<P>, SubParsers<P>>;
7
7
  export function bind<T, U>(parser: Parser<T>, f: (nodes: T[], rest: string, context: Ctx) => Result<U>): Parser<U> {
8
8
  assert(parser);
9
- return ({ source, context }) => {
9
+ return input => {
10
+ const { source, context } = input;
10
11
  if (source === '') return;
11
- const res1 = parser({ source, context });
12
+ const res1 = parser(input);
12
13
  assert(check(source, res1));
13
14
  if (res1 === undefined) return;
14
15
  const res2 = f(eval(res1), exec(res1), context);
@@ -62,14 +62,15 @@ export function creation<P extends Parser<unknown>>(cost: number, recursion: num
62
62
  export function creation(cost: number, recursion: number, parser: Parser<unknown>): Parser<unknown> {
63
63
  assert(cost >= 0);
64
64
  assert(recursion >= 0);
65
- return ({ source, context }) => {
65
+ return input => {
66
+ const { context } = input;
66
67
  const resources = context.resources ?? { clock: cost || 1, recursions: [1] };
67
68
  const { recursions } = resources;
68
69
  assert(recursions.length > 0);
69
70
  const rec = min(recursion, recursions.length);
70
71
  if (rec > 0 && recursions[rec - 1] < 1) throw new Error('Too much recursion');
71
72
  rec > 0 && --recursions[rec - 1];
72
- const result = parser({ source, context });
73
+ const result = parser(input);
73
74
  rec > 0 && ++recursions[rec - 1];
74
75
  if (result === undefined) return;
75
76
  if (resources.clock < cost) throw new Error('Too many creations');
@@ -81,13 +82,14 @@ export function creation(cost: number, recursion: number, parser: Parser<unknown
81
82
  export function precedence<P extends Parser<unknown>>(precedence: number, parser: P): P;
82
83
  export function precedence<T>(precedence: number, parser: Parser<T>): Parser<T> {
83
84
  assert(precedence >= 0);
84
- return ({ source, context }) => {
85
+ return input => {
86
+ const { context } = input;
85
87
  const { delimiters, precedence: p = 0 } = context;
86
88
  const shift = delimiters && precedence > p;
87
89
  context.precedence = precedence;
88
90
  // デリミタはシフト後に設定しなければならない
89
91
  shift && delimiters.shift(precedence);
90
- const result = parser({ source, context });
92
+ const result = parser(input);
91
93
  shift && delimiters.unshift();
92
94
  context.precedence = p;
93
95
  return result;
@@ -103,12 +105,13 @@ export function state<T>(state: number, positive: boolean | Parser<T>, parser?:
103
105
  }
104
106
  assert(state);
105
107
  assert(parser = parser!);
106
- return ({ source, context }) => {
108
+ return input => {
109
+ const { context } = input;
107
110
  const s = context.state ?? 0;
108
111
  context.state = positive
109
112
  ? s | state
110
113
  : s & ~state;
111
- const result = parser({ source, context });
114
+ const result = parser(input);
112
115
  context.state = s;
113
116
  return result;
114
117
  };
@@ -123,12 +126,13 @@ export function constraint<T>(state: number, positive: boolean | Parser<T>, pars
123
126
  }
124
127
  assert(state);
125
128
  assert(parser = parser!);
126
- return ({ source, context }) => {
129
+ return input => {
130
+ const { context } = input;
127
131
  const s = positive
128
132
  ? state & context.state!
129
133
  : state & ~context.state!;
130
134
  return s === state
131
- ? parser({ source, context })
135
+ ? parser(input)
132
136
  : undefined;
133
137
  };
134
138
  }
@@ -7,7 +7,7 @@ import { define } from 'typed-dom/dom';
7
7
 
8
8
  // https://example/@user must be a user page or a redirect page going there.
9
9
 
10
- export const account: AutolinkParser.AccountParser = lazy(() => constraint(State.autolink, false, rewrite(
10
+ export const account: AutolinkParser.AccountParser = lazy(() => rewrite(
11
11
  open(
12
12
  '@',
13
13
  tails([
@@ -15,12 +15,13 @@ export const account: AutolinkParser.AccountParser = lazy(() => constraint(State
15
15
  str(/^[a-z][0-9a-z]*(?:[-.][0-9a-z]+)*/i),
16
16
  ])),
17
17
  union([
18
- state(State.autolink, fmap(convert(
18
+ constraint(State.autolink, false, state(State.autolink, fmap(convert(
19
19
  source =>
20
20
  `[${source}]{ ${source.includes('/')
21
21
  ? `https://${source.slice(1).replace('/', '/@')}`
22
22
  : `/${source}`
23
23
  } }`,
24
24
  unsafelink),
25
- ([el]) => [define(el, { class: 'account' })])),
26
- ]))));
25
+ ([el]) => [define(el, { class: 'account' })]))),
26
+ ({ source }) => [[source], ''],
27
+ ])));
@@ -14,16 +14,17 @@ import { define } from 'typed-dom/dom';
14
14
  // 内部表現はUnixTimeに統一する(時系列順)
15
15
  // 外部表現は投稿ごとに投稿者の投稿時のタイムゾーンに統一する(非時系列順)
16
16
 
17
- export const anchor: AutolinkParser.AnchorParser = lazy(() => constraint(State.autolink, false, validate('>>',
17
+ export const anchor: AutolinkParser.AnchorParser = lazy(() => validate('>>',
18
18
  focus(
19
19
  /^>>(?:[a-z][0-9a-z]*(?:-[0-9a-z]+)*\/)?[0-9a-z]+(?:-[0-9a-z]+)*(?![0-9a-z@#:])/i,
20
20
  union([
21
- state(State.autolink, fmap(convert(
21
+ constraint(State.autolink, false, state(State.autolink, fmap(convert(
22
22
  source =>
23
23
  `[${source}]{ ${source.includes('/')
24
24
  ? `/@${source.slice(2).replace('/', '/timeline?at=')}`
25
25
  : `?at=${source.slice(2)}`
26
26
  } }`,
27
27
  unsafelink),
28
- ([el]) => [define(el, { class: 'anchor' })])),
29
- ])))));
28
+ ([el]) => [define(el, { class: 'anchor' })]))),
29
+ ({ source }) => [[source], ''],
30
+ ]))));
@@ -8,12 +8,13 @@ import { define } from 'typed-dom/dom';
8
8
 
9
9
  // https://example/@user?ch=a+b must be a user channel page or a redirect page going there.
10
10
 
11
- export const channel: AutolinkParser.ChannelParser = constraint(State.autolink, false, validate('@', bind(
11
+ export const channel: AutolinkParser.ChannelParser = validate('@',
12
+ constraint(State.autolink, false, bind(
12
13
  sequence([
13
14
  account,
14
15
  some(hashtag),
15
16
  ]),
16
- (es, rest) => {
17
+ (es: [HTMLAnchorElement], rest) => {
17
18
  const source = stringify(es);
18
19
  const el = es[0];
19
20
  const url = `${el.getAttribute('href')}?ch=${source.slice(source.indexOf('#') + 1).replace(/#/g, '+')}`;
@@ -1,13 +1,16 @@
1
1
  import { AutolinkParser } from '../../inline';
2
2
  import { State, Recursion } from '../../context';
3
- import { creation, state, constraint, verify, rewrite } from '../../../combinator';
3
+ import { union, creation, state, constraint, verify, rewrite } from '../../../combinator';
4
4
  import { str } from '../../source';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
7
  // https://html.spec.whatwg.org/multipage/input.html
8
8
 
9
- export const email: AutolinkParser.EmailParser = constraint(State.autolink, false, creation(1, Recursion.ignore, rewrite(verify(
9
+ export const email: AutolinkParser.EmailParser = creation(1, Recursion.ignore, rewrite(verify(
10
10
  str(/^[0-9a-z](?:[_.+-](?=[0-9a-z])|[0-9a-z]){0,255}@[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])?)*(?![0-9a-z])/i),
11
11
  ([source]) => source.length <= 255),
12
- state(State.autolink,
13
- ({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], '']))));
12
+ union([
13
+ constraint(State.autolink, false, state(State.autolink,
14
+ ({ source }) => [[html('a', { class: 'email', href: `mailto:${source}` }, source)], ''])),
15
+ ({ source }) => [[source], ''],
16
+ ])));
@@ -6,11 +6,12 @@ import { emoji } from './hashtag';
6
6
  import { str } from '../../source';
7
7
  import { define } from 'typed-dom/dom';
8
8
 
9
- export const hashnum: AutolinkParser.HashnumParser = lazy(() => constraint(State.autolink, false, rewrite(
9
+ export const hashnum: AutolinkParser.HashnumParser = lazy(() => rewrite(
10
10
  open('#', str(new RegExp(/^[0-9]{1,9}(?![^\p{C}\p{S}\p{P}\s]|emoji|['_])/u.source.replace(/emoji/, emoji), 'u'))),
11
11
  union([
12
- state(State.autolink, fmap(convert(
12
+ constraint(State.autolink, false, state(State.autolink, fmap(convert(
13
13
  source => `[${source}]{ ${source.slice(1)} }`,
14
14
  unsafelink),
15
- ([el]) => [define(el, { class: 'hashnum', href: null })])),
16
- ]))));
15
+ ([el]) => [define(el, { class: 'hashnum', href: null })]))),
16
+ ({ source }) => [[source], ''],
17
+ ])));
@@ -10,7 +10,7 @@ import { define } from 'typed-dom/dom';
10
10
  // https://github.com/tc39/proposal-regexp-unicode-property-escapes#matching-emoji
11
11
  export const emoji = String.raw`\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F`;
12
12
 
13
- export const hashtag: AutolinkParser.HashtagParser = lazy(() => constraint(State.autolink, false, rewrite(
13
+ export const hashtag: AutolinkParser.HashtagParser = lazy(() => rewrite(
14
14
  open(
15
15
  '#',
16
16
  str(new RegExp([
@@ -18,8 +18,9 @@ export const hashtag: AutolinkParser.HashtagParser = lazy(() => constraint(State
18
18
  /(?:[^\p{C}\p{S}\p{P}\s]|emoji|'|_(?=[^\p{C}\p{S}\p{P}\s]|emoji|'))+/u.source,
19
19
  ].join('').replace(/emoji/g, emoji), 'u'))),
20
20
  union([
21
- state(State.autolink, fmap(convert(
21
+ constraint(State.autolink, false, state(State.autolink, fmap(convert(
22
22
  source => `[${source}]{ ${`/hashtags/${source.slice(1)}`} }`,
23
23
  unsafelink),
24
- ([el]) => [define(el, { class: 'hashtag' })])),
25
- ]))));
24
+ ([el]) => [define(el, { class: 'hashtag' })]))),
25
+ ({ source }) => [[source], ''],
26
+ ])));
@@ -16,7 +16,7 @@ describe('Unit: parser/inline/autolink/url', () => {
16
16
  assert.deepStrictEqual(inspect(parser('Http://host')), [['Http'], '://host']);
17
17
  //assert.deepStrictEqual(inspect(parser('http://[::ffff:0:0%1]')), [['<a class="invalid">http://[::ffff:0:0%1]</a>'], '']);
18
18
  //assert.deepStrictEqual(inspect(parser('http://[::ffff:0:0/96]')), [['<a class="invalid">http://[::ffff:0:0/96]</a>'], '']);
19
- assert.deepStrictEqual(inspect(parser(' http://a')), undefined);
19
+ assert.deepStrictEqual(inspect(parser(' http://host')), undefined);
20
20
  });
21
21
 
22
22
  it('basic', () => {
@@ -1,12 +1,12 @@
1
1
  import { CodeParser } from '../inline';
2
2
  import { Recursion } from '../context';
3
- import { creation, validate, match } from '../../combinator';
3
+ import { creation, match } from '../../combinator';
4
4
  import { html } from 'typed-dom/dom';
5
5
 
6
- export const code: CodeParser = creation(1, Recursion.ignore, validate('`', match(
6
+ export const code: CodeParser = creation(1, Recursion.ignore, match(
7
7
  /^(`+)(?!`)([^\n]*?[^`\n])\1(?!`)/,
8
8
  ([whole, , body]) => ({ source }) =>
9
- [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)])));
9
+ [[html('code', { 'data-src': whole }, format(body))], source.slice(whole.length)]));
10
10
 
11
11
  function format(text: string): string {
12
12
  assert(text.length > 0);
@@ -9,7 +9,7 @@ import { html, define, defrag } from 'typed-dom/dom';
9
9
 
10
10
  import IndexParser = ExtensionParser.IndexParser;
11
11
 
12
- export const index: IndexParser = lazy(() => validate('[#', constraint(State.index, false, creation(1, Recursion.ignore, fmap(indexee(surround(
12
+ export const index: IndexParser = lazy(() => constraint(State.index, false, creation(1, Recursion.ignore, fmap(indexee(surround(
13
13
  '[#',
14
14
  precedence(1, state(State.linkers | State.media,
15
15
  startTight(
@@ -31,7 +31,7 @@ export const index: IndexParser = lazy(() => validate('[#', constraint(State.ind
31
31
  class: 'index',
32
32
  href: el.id ? `#${el.id}` : undefined,
33
33
  }),
34
- ])))));
34
+ ]))));
35
35
 
36
36
  export const signature: IndexParser.SignatureParser = lazy(() => validate('|', creation(1, Recursion.ignore, fmap(open(
37
37
  /^\|(?!\\?\s)/,
@@ -1,24 +1,24 @@
1
1
  import { ExtensionParser } from '../../inline';
2
2
  import { State, Recursion } from '../../context';
3
- import { union, constraint, creation, validate, surround, clear, fmap } from '../../../combinator';
3
+ import { union, constraint, creation, surround, clear, fmap } from '../../../combinator';
4
4
  import { str } from '../../source';
5
5
  import { html } from 'typed-dom/dom';
6
6
 
7
7
  const body = str(/^\$[A-Za-z]*(?:(?:-[A-Za-z][0-9A-Za-z]*)+|-(?:(?:0|[1-9][0-9]*)\.)*(?:0|[1-9][0-9]*)(?![0-9A-Za-z]))/);
8
8
 
9
- export const segment: ExtensionParser.LabelParser.SegmentParser = clear(validate(['[$', '$'], union([
9
+ export const segment: ExtensionParser.LabelParser.SegmentParser = clear(union([
10
10
  surround('[', body, ']'),
11
11
  body,
12
- ])));
12
+ ]));
13
13
 
14
- export const label: ExtensionParser.LabelParser = constraint(State.label, false, validate(['[$', '$'], creation(1, Recursion.ignore, fmap(
14
+ export const label: ExtensionParser.LabelParser = constraint(State.label, false, creation(1, Recursion.ignore, fmap(
15
15
  union([
16
16
  surround('[', body, ']'),
17
17
  body,
18
18
  ]),
19
19
  ([text]) => [
20
20
  html('a', { class: 'label', 'data-label': text.slice(text[1] === '-' ? 0 : 1).toLowerCase() }, text),
21
- ]))));
21
+ ])));
22
22
 
23
23
  export function number(label: string, base: string): string {
24
24
  return isFixed(label)
@@ -1,6 +1,6 @@
1
1
  import { ExtensionParser } from '../../inline';
2
2
  import { Recursion, Backtrack } from '../../context';
3
- import { union, some, creation, precedence, validate, surround, lazy } from '../../../combinator';
3
+ import { union, some, creation, precedence, surround, lazy } from '../../../combinator';
4
4
  import { inline } from '../../inline';
5
5
  import { str } from '../../source';
6
6
  import { startTight } from '../../visibility';
@@ -11,7 +11,7 @@ import { html, defrag } from 'typed-dom/dom';
11
11
 
12
12
  // All syntax surrounded by square brackets shouldn't contain line breaks.
13
13
 
14
- export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validate('[', creation(1, Recursion.inline, surround(
14
+ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => creation(1, Recursion.inline, surround(
15
15
  str(/^\[[:^|]/),
16
16
  precedence(1,
17
17
  startTight(some(union([inline]), ']', [[']', 1]]))),
@@ -24,4 +24,4 @@ export const placeholder: ExtensionParser.PlaceholderParser = lazy(() => validat
24
24
  'data-invalid-message': `Invalid start symbol or linebreak`,
25
25
  }, defrag(bs)),
26
26
  ], rest],
27
- ([as, bs], rest) => [unshift(as, bs), rest], 3 | Backtrack.bracket))));
27
+ ([as, bs], rest) => [unshift(as, bs), rest], 3 | Backtrack.bracket)));
@@ -1,11 +1,11 @@
1
1
  import { ExtensionParser } from '../inline';
2
- import { union, validate } from '../../combinator';
2
+ import { union } from '../../combinator';
3
3
  import { index } from './extension/index';
4
4
  import { label } from './extension/label';
5
5
  import { placeholder } from './extension/placeholder';
6
6
 
7
- export const extension: ExtensionParser = validate(['[', '$'], union([
7
+ export const extension: ExtensionParser = union([
8
8
  index,
9
9
  label,
10
10
  placeholder,
11
- ]));
11
+ ]);
@@ -18,7 +18,7 @@ const attrspecs = {
18
18
  Object.setPrototypeOf(attrspecs, null);
19
19
  Object.values(attrspecs).forEach(o => Object.setPrototypeOf(o, null));
20
20
 
21
- export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^\S\n]|>)/i, creation(1, Recursion.inline,
21
+ export const html: HTMLParser = lazy(() => validate(/^<[a-z]+(?=[^\S\n]|>)/i, creation(1, Recursion.inline,
22
22
  union([
23
23
  focus(
24
24
  /^<wbr[^\S\n]*>/i,
@@ -66,7 +66,7 @@ export const html: HTMLParser = lazy(() => validate('<', validate(/^<[a-z]+(?=[^
66
66
  [[elem(tag, as, bs, [])], rest]),
67
67
  ([, tag]) => tag,
68
68
  new Clock(10000))),
69
- ])))));
69
+ ]))));
70
70
 
71
71
  export const attribute: HTMLParser.AttributeParser = union([
72
72
  str(/^[^\S\n]+[a-z]+(?:-[a-z]+)*(?:="[^"\n]*")?(?=[^\S\n]|>)/i),
@@ -0,0 +1,53 @@
1
+ import { italic } from './italic';
2
+ import { some } from '../../combinator';
3
+ import { inspect } from '../../debug.test';
4
+
5
+ describe('Unit: parser/inline/italic', () => {
6
+ describe('italic', () => {
7
+ const parser = (source: string) => some(italic)({ source, context: {} });
8
+
9
+ it('invalid', () => {
10
+ assert.deepStrictEqual(inspect(parser('///')), undefined);
11
+ assert.deepStrictEqual(inspect(parser('///a')), [['///', 'a'], '']);
12
+ assert.deepStrictEqual(inspect(parser('///a ///')), [['///', 'a'], ' ///']);
13
+ assert.deepStrictEqual(inspect(parser('///a ///')), [['///', 'a', ' '], ' ///']);
14
+ assert.deepStrictEqual(inspect(parser('///a\n///')), [['///', 'a'], '\n///']);
15
+ assert.deepStrictEqual(inspect(parser('///a\\ ///')), [['///', 'a'], '\\ ///']);
16
+ assert.deepStrictEqual(inspect(parser('///a\\\n///')), [['///', 'a'], '\\\n///']);
17
+ assert.deepStrictEqual(inspect(parser('///a/b')), [['///', 'a', '/', 'b'], '']);
18
+ assert.deepStrictEqual(inspect(parser('///a//b')), [['///', 'a', '//', 'b'], '']);
19
+ assert.deepStrictEqual(inspect(parser('///a*b///')), [['///', 'a', '*', 'b', '///'], '']);
20
+ assert.deepStrictEqual(inspect(parser('/// ///')), undefined);
21
+ assert.deepStrictEqual(inspect(parser('/// a///')), undefined);
22
+ assert.deepStrictEqual(inspect(parser('/// a ///')), undefined);
23
+ assert.deepStrictEqual(inspect(parser('///\n///')), undefined);
24
+ assert.deepStrictEqual(inspect(parser('///\na///')), undefined);
25
+ assert.deepStrictEqual(inspect(parser('///\\ a///')), undefined);
26
+ assert.deepStrictEqual(inspect(parser('///\\\na///')), undefined);
27
+ assert.deepStrictEqual(inspect(parser('///<wbr>a///')), undefined);
28
+ assert.deepStrictEqual(inspect(parser('////a////')), undefined);
29
+ assert.deepStrictEqual(inspect(parser('/////a/////')), undefined);
30
+ assert.deepStrictEqual(inspect(parser(' ///a///')), undefined);
31
+ });
32
+
33
+ it('basic', () => {
34
+ assert.deepStrictEqual(inspect(parser('///a///')), [['<i>a</i>'], '']);
35
+ assert.deepStrictEqual(inspect(parser('///ab///')), [['<i>ab</i>'], '']);
36
+ assert.deepStrictEqual(inspect(parser('///a////')), [['<i>a</i>'], '/']);
37
+ assert.deepStrictEqual(inspect(parser('///a\nb///')), [['<i>a<br>b</i>'], '']);
38
+ assert.deepStrictEqual(inspect(parser('///a\\\nb///')), [['<i>a<br>b</i>'], '']);
39
+ });
40
+
41
+ it('nest', () => {
42
+ assert.deepStrictEqual(inspect(parser('///a ///b//////')), [['<i>a <i>b</i></i>'], '']);
43
+ assert.deepStrictEqual(inspect(parser('///a\\ ///b//////')), [['<i>a <i>b</i></i>'], '']);
44
+ assert.deepStrictEqual(inspect(parser('///a&Tab;///b//////')), [['<i>a\t<i>b</i></i>'], '']);
45
+ assert.deepStrictEqual(inspect(parser('///a<wbr>///b//////')), [['<i>a<wbr><i>b</i></i>'], '']);
46
+ assert.deepStrictEqual(inspect(parser('///`a`///')), [['<i><code data-src="`a`">a</code></i>'], '']);
47
+ assert.deepStrictEqual(inspect(parser('///(///a///)///')), [['<i><span class="paren">(<i>a</i>)</span></i>'], '']);
48
+ assert.deepStrictEqual(inspect(parser('///{http://host/}///')), [['<i><a class="url" href="http://host/" target="_blank">http://host/</a></i>'], '']);
49
+ });
50
+
51
+ });
52
+
53
+ });