marked 1.2.7 → 2.0.1

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.
package/src/Tokenizer.js CHANGED
@@ -79,19 +79,10 @@ module.exports = class Tokenizer {
79
79
  }
80
80
  }
81
81
 
82
- code(src, tokens) {
82
+ code(src) {
83
83
  const cap = this.rules.block.code.exec(src);
84
84
  if (cap) {
85
- const lastToken = tokens[tokens.length - 1];
86
- // An indented code block cannot interrupt a paragraph.
87
- if (lastToken && lastToken.type === 'paragraph') {
88
- return {
89
- raw: cap[0],
90
- text: cap[0].trimRight()
91
- };
92
- }
93
-
94
- const text = cap[0].replace(/^ {4}/gm, '');
85
+ const text = cap[0].replace(/^ {1,4}/gm, '');
95
86
  return {
96
87
  type: 'code',
97
88
  raw: cap[0],
@@ -229,7 +220,8 @@ module.exports = class Tokenizer {
229
220
  addBack,
230
221
  loose,
231
222
  istask,
232
- ischecked;
223
+ ischecked,
224
+ endMatch;
233
225
 
234
226
  let l = itemMatch.length;
235
227
  bcurr = this.rules.block.listItemStart.exec(itemMatch[0]);
@@ -237,28 +229,42 @@ module.exports = class Tokenizer {
237
229
  item = itemMatch[i];
238
230
  raw = item;
239
231
 
232
+ if (!this.options.pedantic) {
233
+ // Determine if current item contains the end of the list
234
+ endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S'));
235
+ if (endMatch) {
236
+ addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length;
237
+ list.raw = list.raw.substring(0, list.raw.length - addBack);
238
+
239
+ item = item.substring(0, endMatch.index);
240
+ raw = item;
241
+ l = i + 1;
242
+ }
243
+ }
244
+
240
245
  // Determine whether the next list item belongs here.
241
246
  // Backpedal if it does not belong in this list.
242
247
  if (i !== l - 1) {
243
248
  bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
244
-
245
- if (bnext[1].length > bcurr[0].length || bnext[1].length > 3) {
246
- // nested list
247
- itemMatch.splice(i, 2, itemMatch[i] + '\n' + itemMatch[i + 1]);
249
+ if (
250
+ !this.options.pedantic
251
+ ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3
252
+ : bnext[1].length > bcurr[1].length
253
+ ) {
254
+ // nested list or continuation
255
+ itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]);
248
256
  i--;
249
257
  l--;
250
258
  continue;
251
- } else {
252
- if (
253
- // different bullet style
254
- !this.options.pedantic || this.options.smartLists
255
- ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1]
256
- : isordered === (bnext[2].length === 1)
257
- ) {
258
- addBack = itemMatch.slice(i + 1).join('\n');
259
- list.raw = list.raw.substring(0, list.raw.length - addBack.length);
260
- i = l - 1;
261
- }
259
+ } else if (
260
+ // different bullet style
261
+ !this.options.pedantic || this.options.smartLists
262
+ ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1]
263
+ : isordered === (bnext[2].length === 1)
264
+ ) {
265
+ addBack = itemMatch.slice(i + 1).join('\n').length;
266
+ list.raw = list.raw.substring(0, list.raw.length - addBack);
267
+ i = l - 1;
262
268
  }
263
269
  bcurr = bnext;
264
270
  }
@@ -277,12 +283,18 @@ module.exports = class Tokenizer {
277
283
  : item.replace(/^ {1,4}/gm, '');
278
284
  }
279
285
 
286
+ // trim item newlines at end
287
+ item = rtrim(item, '\n');
288
+ if (i !== l - 1) {
289
+ raw = raw + '\n';
290
+ }
291
+
280
292
  // Determine whether item is loose or not.
281
293
  // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
282
294
  // for discount behavior.
283
- loose = next || /\n\n(?!\s*$)/.test(item);
295
+ loose = next || /\n\n(?!\s*$)/.test(raw);
284
296
  if (i !== l - 1) {
285
- next = item.charAt(item.length - 1) === '\n';
297
+ next = raw.slice(-2) === '\n\n';
286
298
  if (!loose) loose = next;
287
299
  }
288
300
 
@@ -407,17 +419,9 @@ module.exports = class Tokenizer {
407
419
  }
408
420
  }
409
421
 
410
- text(src, tokens) {
422
+ text(src) {
411
423
  const cap = this.rules.block.text.exec(src);
412
424
  if (cap) {
413
- const lastToken = tokens[tokens.length - 1];
414
- if (lastToken && lastToken.type === 'text') {
415
- return {
416
- raw: cap[0],
417
- text: cap[0]
418
- };
419
- }
420
-
421
425
  return {
422
426
  type: 'text',
423
427
  raw: cap[0],
@@ -541,46 +545,61 @@ module.exports = class Tokenizer {
541
545
  }
542
546
  }
543
547
 
544
- strong(src, maskedSrc, prevChar = '') {
545
- let match = this.rules.inline.strong.start.exec(src);
548
+ emStrong(src, maskedSrc, prevChar = '') {
549
+ let match = this.rules.inline.emStrong.lDelim.exec(src);
550
+ if (!match) return;
551
+
552
+ if (match[3] && prevChar.match(/[\p{L}\p{N}]/u)) return; // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
553
+
554
+ const nextChar = match[1] || match[2] || '';
546
555
 
547
- if (match && (!match[1] || (match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))))) {
548
- maskedSrc = maskedSrc.slice(-1 * src.length);
549
- const endReg = match[0] === '**' ? this.rules.inline.strong.endAst : this.rules.inline.strong.endUnd;
556
+ if (!nextChar || (nextChar && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar)))) {
557
+ const lLength = match[0].length - 1;
558
+ let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
550
559
 
560
+ const endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd;
551
561
  endReg.lastIndex = 0;
552
562
 
553
- let cap;
563
+ maskedSrc = maskedSrc.slice(-1 * src.length + lLength); // Bump maskedSrc to same section of string as src (move to lexer?)
564
+
554
565
  while ((match = endReg.exec(maskedSrc)) != null) {
555
- cap = this.rules.inline.strong.middle.exec(maskedSrc.slice(0, match.index + 3));
556
- if (cap) {
557
- return {
558
- type: 'strong',
559
- raw: src.slice(0, cap[0].length),
560
- text: src.slice(2, cap[0].length - 2)
561
- };
566
+ rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
567
+
568
+ if (!rDelim) continue; // matched the first alternative in rules.js (skip the * in __abc*abc__)
569
+
570
+ rLength = rDelim.length;
571
+
572
+ if (match[3] || match[4]) { // found another Left Delim
573
+ delimTotal += rLength;
574
+ continue;
575
+ } else if (match[5] || match[6]) { // either Left or Right Delim
576
+ if (lLength % 3 && !((lLength + rLength) % 3)) {
577
+ midDelimTotal += rLength;
578
+ continue; // CommonMark Emphasis Rules 9-10
579
+ }
562
580
  }
563
- }
564
- }
565
- }
566
581
 
567
- em(src, maskedSrc, prevChar = '') {
568
- let match = this.rules.inline.em.start.exec(src);
582
+ delimTotal -= rLength;
569
583
 
570
- if (match && (!match[1] || (match[1] && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))))) {
571
- maskedSrc = maskedSrc.slice(-1 * src.length);
572
- const endReg = match[0] === '*' ? this.rules.inline.em.endAst : this.rules.inline.em.endUnd;
584
+ if (delimTotal > 0) continue; // Haven't found enough closing delimiters
573
585
 
574
- endReg.lastIndex = 0;
586
+ // If this is the last rDelimiter, remove extra characters. *a*** -> *a*
587
+ if (delimTotal + midDelimTotal - rLength <= 0 && !maskedSrc.slice(endReg.lastIndex).match(endReg)) {
588
+ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
589
+ }
575
590
 
576
- let cap;
577
- while ((match = endReg.exec(maskedSrc)) != null) {
578
- cap = this.rules.inline.em.middle.exec(maskedSrc.slice(0, match.index + 2));
579
- if (cap) {
591
+ if (Math.min(lLength, rLength) % 2) {
580
592
  return {
581
593
  type: 'em',
582
- raw: src.slice(0, cap[0].length),
583
- text: src.slice(1, cap[0].length - 1)
594
+ raw: src.slice(0, lLength + match.index + rLength + 1),
595
+ text: src.slice(1, lLength + match.index + rLength)
596
+ };
597
+ }
598
+ if (Math.min(lLength, rLength) % 2 === 0) {
599
+ return {
600
+ type: 'strong',
601
+ raw: src.slice(0, lLength + match.index + rLength + 1),
602
+ text: src.slice(2, lLength + match.index + rLength - 1)
584
603
  };
585
604
  }
586
605
  }
package/src/rules.js CHANGED
@@ -8,8 +8,8 @@ const {
8
8
  * Block-Level Grammar
9
9
  */
10
10
  const block = {
11
- newline: /^\n+/,
12
- code: /^( {4}[^\n]+\n*)+/,
11
+ newline: /^(?: *(?:\n|$))+/,
12
+ code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
13
13
  fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
14
14
  hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
15
15
  heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
@@ -31,7 +31,7 @@ const block = {
31
31
  lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,
32
32
  // regex template, placeholders will be replaced according to different paragraph
33
33
  // interruption rules of commonmark and the original markdown spec:
34
- _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,
34
+ _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,
35
35
  text: /^[^\n]+/
36
36
  };
37
37
 
@@ -48,7 +48,7 @@ block.item = edit(block.item, 'gm')
48
48
  .replace(/bull/g, block.bullet)
49
49
  .getRegex();
50
50
 
51
- block.listItemStart = edit(/^( *)(bull)/)
51
+ block.listItemStart = edit(/^( *)(bull) */)
52
52
  .replace('bull', block.bullet)
53
53
  .getRegex();
54
54
 
@@ -173,74 +173,41 @@ const inline = {
173
173
  reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
174
174
  nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
175
175
  reflinkSearch: 'reflink|nolink(?!\\()',
176
- strong: {
177
- start: /^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/, // (1) returns if starts w/ punctuation
178
- middle: /^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,
179
- endAst: /[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline)
180
- endUnd: /[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
181
- },
182
- em: {
183
- start: /^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/, // (1) returns if starts w/ punctuation
184
- middle: /^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,
185
- endAst: /[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/, // last char can't be punct, or final * must also be followed by punct (or endline)
186
- endUnd: /[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/ // last char can't be a space, and final _ must preceed punct or \s (or endline)
176
+ emStrong: {
177
+ lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
178
+ // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
179
+ // () Skip other delimiter (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
180
+ rDelimAst: /\_\_[^_]*?\*[^_]*?\_\_|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
181
+ rDelimUnd: /\*\*[^*]*?\_[^*]*?\*\*|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
187
182
  },
188
183
  code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
189
184
  br: /^( {2,}|\\)\n(?!\s*$)/,
190
185
  del: noopTest,
191
- text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*]|\b_|$)|[^ ](?= {2,}\n)))/,
192
- punctuation: /^([\s*punctuation])/
186
+ text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,
187
+ punctuation: /^([\spunctuation])/
193
188
  };
194
189
 
195
- // list of punctuation marks from common mark spec
196
- // without * and _ to workaround cases with double emphasis
190
+ // list of punctuation marks from CommonMark spec
191
+ // without * and _ to handle the different emphasis markers * and _
197
192
  inline._punctuation = '!"#$%&\'()+\\-.,/:;<=>?@\\[\\]`^{|}~';
198
193
  inline.punctuation = edit(inline.punctuation).replace(/punctuation/g, inline._punctuation).getRegex();
199
194
 
200
195
  // sequences em should skip over [title](link), `code`, <html>
201
- inline._blockSkip = '\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>';
202
- inline._overlapSkip = '__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*';
196
+ inline.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g;
197
+ inline.escapedEmSt = /\\\*|\\_/g;
203
198
 
204
199
  inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex();
205
200
 
206
- inline.em.start = edit(inline.em.start)
207
- .replace(/punctuation/g, inline._punctuation)
208
- .getRegex();
209
-
210
- inline.em.middle = edit(inline.em.middle)
211
- .replace(/punctuation/g, inline._punctuation)
212
- .replace(/overlapSkip/g, inline._overlapSkip)
213
- .getRegex();
214
-
215
- inline.em.endAst = edit(inline.em.endAst, 'g')
216
- .replace(/punctuation/g, inline._punctuation)
217
- .getRegex();
218
-
219
- inline.em.endUnd = edit(inline.em.endUnd, 'g')
220
- .replace(/punctuation/g, inline._punctuation)
221
- .getRegex();
222
-
223
- inline.strong.start = edit(inline.strong.start)
224
- .replace(/punctuation/g, inline._punctuation)
225
- .getRegex();
226
-
227
- inline.strong.middle = edit(inline.strong.middle)
228
- .replace(/punctuation/g, inline._punctuation)
229
- .replace(/overlapSkip/g, inline._overlapSkip)
230
- .getRegex();
231
-
232
- inline.strong.endAst = edit(inline.strong.endAst, 'g')
233
- .replace(/punctuation/g, inline._punctuation)
234
- .getRegex();
235
-
236
- inline.strong.endUnd = edit(inline.strong.endUnd, 'g')
237
- .replace(/punctuation/g, inline._punctuation)
201
+ inline.emStrong.lDelim = edit(inline.emStrong.lDelim)
202
+ .replace(/punct/g, inline._punctuation)
238
203
  .getRegex();
239
204
 
240
- inline.blockSkip = edit(inline._blockSkip, 'g')
205
+ inline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, 'g')
206
+ .replace(/punct/g, inline._punctuation)
241
207
  .getRegex();
242
208
 
243
- inline.overlapSkip = edit(inline._overlapSkip, 'g')
209
+ inline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, 'g')
210
+ .replace(/punct/g, inline._punctuation)
244
211
  .getRegex();
245
212
 
246
213
  inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
@@ -319,7 +286,7 @@ inline.gfm = merge({}, inline.normal, {
319
286
  url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
320
287
  _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
321
288
  del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
322
- text: /^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*~]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/
289
+ text: /^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/
323
290
  });
324
291
 
325
292
  inline.gfm.url = edit(inline.gfm.url, 'i')