marked 0.8.1 → 1.1.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/Lexer.js CHANGED
@@ -1,10 +1,47 @@
1
+ const Tokenizer = require('./Tokenizer.js');
1
2
  const { defaults } = require('./defaults.js');
2
- const { block } = require('./rules.js');
3
- const {
4
- rtrim,
5
- splitCells,
6
- escape
7
- } = require('./helpers.js');
3
+ const { block, inline } = require('./rules.js');
4
+
5
+ /**
6
+ * smartypants text replacement
7
+ */
8
+ function smartypants(text) {
9
+ return text
10
+ // em-dashes
11
+ .replace(/---/g, '\u2014')
12
+ // en-dashes
13
+ .replace(/--/g, '\u2013')
14
+ // opening singles
15
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
16
+ // closing singles & apostrophes
17
+ .replace(/'/g, '\u2019')
18
+ // opening doubles
19
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
20
+ // closing doubles
21
+ .replace(/"/g, '\u201d')
22
+ // ellipses
23
+ .replace(/\.{3}/g, '\u2026');
24
+ }
25
+
26
+ /**
27
+ * mangle email addresses
28
+ */
29
+ function mangle(text) {
30
+ let out = '',
31
+ i,
32
+ ch;
33
+
34
+ const l = text.length;
35
+ for (i = 0; i < l; i++) {
36
+ ch = text.charCodeAt(i);
37
+ if (Math.random() > 0.5) {
38
+ ch = 'x' + ch.toString(16);
39
+ }
40
+ out += '&#' + ch + ';';
41
+ }
42
+
43
+ return out;
44
+ }
8
45
 
9
46
  /**
10
47
  * Block Lexer
@@ -14,20 +51,37 @@ module.exports = class Lexer {
14
51
  this.tokens = [];
15
52
  this.tokens.links = Object.create(null);
16
53
  this.options = options || defaults;
17
- this.rules = block.normal;
54
+ this.options.tokenizer = this.options.tokenizer || new Tokenizer();
55
+ this.tokenizer = this.options.tokenizer;
56
+ this.tokenizer.options = this.options;
57
+
58
+ const rules = {
59
+ block: block.normal,
60
+ inline: inline.normal
61
+ };
18
62
 
19
63
  if (this.options.pedantic) {
20
- this.rules = block.pedantic;
64
+ rules.block = block.pedantic;
65
+ rules.inline = inline.pedantic;
21
66
  } else if (this.options.gfm) {
22
- this.rules = block.gfm;
67
+ rules.block = block.gfm;
68
+ if (this.options.breaks) {
69
+ rules.inline = inline.breaks;
70
+ } else {
71
+ rules.inline = inline.gfm;
72
+ }
23
73
  }
74
+ this.tokenizer.rules = rules;
24
75
  }
25
76
 
26
77
  /**
27
- * Expose Block Rules
78
+ * Expose Rules
28
79
  */
29
80
  static get rules() {
30
- return block;
81
+ return {
82
+ block,
83
+ inline
84
+ };
31
85
  }
32
86
 
33
87
  /**
@@ -36,7 +90,7 @@ module.exports = class Lexer {
36
90
  static lex(src, options) {
37
91
  const lexer = new Lexer(options);
38
92
  return lexer.lex(src);
39
- };
93
+ }
40
94
 
41
95
  /**
42
96
  * Preprocessing
@@ -46,357 +100,356 @@ module.exports = class Lexer {
46
100
  .replace(/\r\n|\r/g, '\n')
47
101
  .replace(/\t/g, ' ');
48
102
 
49
- return this.token(src, true);
50
- };
103
+ this.blockTokens(src, this.tokens, true);
104
+
105
+ this.inline(this.tokens);
106
+
107
+ return this.tokens;
108
+ }
51
109
 
52
110
  /**
53
111
  * Lexing
54
112
  */
55
- token(src, top) {
113
+ blockTokens(src, tokens = [], top = true) {
56
114
  src = src.replace(/^ +$/gm, '');
57
- let next,
58
- loose,
59
- cap,
60
- bull,
61
- b,
62
- item,
63
- listStart,
64
- listItems,
65
- t,
66
- space,
67
- i,
68
- tag,
69
- l,
70
- isordered,
71
- istask,
72
- ischecked;
115
+ let token, i, l, lastToken;
73
116
 
74
117
  while (src) {
75
118
  // newline
76
- if (cap = this.rules.newline.exec(src)) {
77
- src = src.substring(cap[0].length);
78
- if (cap[0].length > 1) {
79
- this.tokens.push({
80
- type: 'space'
81
- });
119
+ if (token = this.tokenizer.space(src)) {
120
+ src = src.substring(token.raw.length);
121
+ if (token.type) {
122
+ tokens.push(token);
82
123
  }
124
+ continue;
83
125
  }
84
126
 
85
127
  // code
86
- if (cap = this.rules.code.exec(src)) {
87
- const lastToken = this.tokens[this.tokens.length - 1];
88
- src = src.substring(cap[0].length);
89
- // An indented code block cannot interrupt a paragraph.
90
- if (lastToken && lastToken.type === 'paragraph') {
91
- lastToken.text += '\n' + cap[0].trimRight();
128
+ if (token = this.tokenizer.code(src, tokens)) {
129
+ src = src.substring(token.raw.length);
130
+ if (token.type) {
131
+ tokens.push(token);
92
132
  } else {
93
- cap = cap[0].replace(/^ {4}/gm, '');
94
- this.tokens.push({
95
- type: 'code',
96
- codeBlockStyle: 'indented',
97
- text: !this.options.pedantic
98
- ? rtrim(cap, '\n')
99
- : cap
100
- });
133
+ lastToken = tokens[tokens.length - 1];
134
+ lastToken.raw += '\n' + token.raw;
135
+ lastToken.text += '\n' + token.text;
101
136
  }
102
137
  continue;
103
138
  }
104
139
 
105
140
  // fences
106
- if (cap = this.rules.fences.exec(src)) {
107
- src = src.substring(cap[0].length);
108
- this.tokens.push({
109
- type: 'code',
110
- lang: cap[2] ? cap[2].trim() : cap[2],
111
- text: cap[3] || ''
112
- });
141
+ if (token = this.tokenizer.fences(src)) {
142
+ src = src.substring(token.raw.length);
143
+ tokens.push(token);
113
144
  continue;
114
145
  }
115
146
 
116
147
  // heading
117
- if (cap = this.rules.heading.exec(src)) {
118
- src = src.substring(cap[0].length);
119
- this.tokens.push({
120
- type: 'heading',
121
- depth: cap[1].length,
122
- text: cap[2]
123
- });
148
+ if (token = this.tokenizer.heading(src)) {
149
+ src = src.substring(token.raw.length);
150
+ tokens.push(token);
124
151
  continue;
125
152
  }
126
153
 
127
154
  // table no leading pipe (gfm)
128
- if (cap = this.rules.nptable.exec(src)) {
129
- item = {
130
- type: 'table',
131
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
132
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
133
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
134
- };
135
-
136
- if (item.header.length === item.align.length) {
137
- src = src.substring(cap[0].length);
138
-
139
- for (i = 0; i < item.align.length; i++) {
140
- if (/^ *-+: *$/.test(item.align[i])) {
141
- item.align[i] = 'right';
142
- } else if (/^ *:-+: *$/.test(item.align[i])) {
143
- item.align[i] = 'center';
144
- } else if (/^ *:-+ *$/.test(item.align[i])) {
145
- item.align[i] = 'left';
146
- } else {
147
- item.align[i] = null;
148
- }
149
- }
150
-
151
- for (i = 0; i < item.cells.length; i++) {
152
- item.cells[i] = splitCells(item.cells[i], item.header.length);
153
- }
154
-
155
- this.tokens.push(item);
156
-
157
- continue;
158
- }
155
+ if (token = this.tokenizer.nptable(src)) {
156
+ src = src.substring(token.raw.length);
157
+ tokens.push(token);
158
+ continue;
159
159
  }
160
160
 
161
161
  // hr
162
- if (cap = this.rules.hr.exec(src)) {
163
- src = src.substring(cap[0].length);
164
- this.tokens.push({
165
- type: 'hr'
166
- });
162
+ if (token = this.tokenizer.hr(src)) {
163
+ src = src.substring(token.raw.length);
164
+ tokens.push(token);
167
165
  continue;
168
166
  }
169
167
 
170
168
  // blockquote
171
- if (cap = this.rules.blockquote.exec(src)) {
172
- src = src.substring(cap[0].length);
173
-
174
- this.tokens.push({
175
- type: 'blockquote_start'
176
- });
169
+ if (token = this.tokenizer.blockquote(src)) {
170
+ src = src.substring(token.raw.length);
171
+ token.tokens = this.blockTokens(token.text, [], top);
172
+ tokens.push(token);
173
+ continue;
174
+ }
177
175
 
178
- cap = cap[0].replace(/^ *> ?/gm, '');
176
+ // list
177
+ if (token = this.tokenizer.list(src)) {
178
+ src = src.substring(token.raw.length);
179
+ l = token.items.length;
180
+ for (i = 0; i < l; i++) {
181
+ token.items[i].tokens = this.blockTokens(token.items[i].text, [], false);
182
+ }
183
+ tokens.push(token);
184
+ continue;
185
+ }
179
186
 
180
- // Pass `top` to keep the current
181
- // "toplevel" state. This is exactly
182
- // how markdown.pl works.
183
- this.token(cap, top);
187
+ // html
188
+ if (token = this.tokenizer.html(src)) {
189
+ src = src.substring(token.raw.length);
190
+ tokens.push(token);
191
+ continue;
192
+ }
184
193
 
185
- this.tokens.push({
186
- type: 'blockquote_end'
187
- });
194
+ // def
195
+ if (top && (token = this.tokenizer.def(src))) {
196
+ src = src.substring(token.raw.length);
197
+ if (!this.tokens.links[token.tag]) {
198
+ this.tokens.links[token.tag] = {
199
+ href: token.href,
200
+ title: token.title
201
+ };
202
+ }
203
+ continue;
204
+ }
188
205
 
206
+ // table (gfm)
207
+ if (token = this.tokenizer.table(src)) {
208
+ src = src.substring(token.raw.length);
209
+ tokens.push(token);
189
210
  continue;
190
211
  }
191
212
 
192
- // list
193
- if (cap = this.rules.list.exec(src)) {
194
- src = src.substring(cap[0].length);
195
- bull = cap[2];
196
- isordered = bull.length > 1;
197
-
198
- listStart = {
199
- type: 'list_start',
200
- ordered: isordered,
201
- start: isordered ? +bull : '',
202
- loose: false
203
- };
204
-
205
- this.tokens.push(listStart);
206
-
207
- // Get each top-level item.
208
- cap = cap[0].match(this.rules.item);
209
-
210
- listItems = [];
211
- next = false;
212
- l = cap.length;
213
- i = 0;
214
-
215
- for (; i < l; i++) {
216
- item = cap[i];
217
-
218
- // Remove the list item's bullet
219
- // so it is seen as the next token.
220
- space = item.length;
221
- item = item.replace(/^ *([*+-]|\d+\.) */, '');
222
-
223
- // Outdent whatever the
224
- // list item contains. Hacky.
225
- if (~item.indexOf('\n ')) {
226
- space -= item.length;
227
- item = !this.options.pedantic
228
- ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
229
- : item.replace(/^ {1,4}/gm, '');
230
- }
213
+ // lheading
214
+ if (token = this.tokenizer.lheading(src)) {
215
+ src = src.substring(token.raw.length);
216
+ tokens.push(token);
217
+ continue;
218
+ }
231
219
 
232
- // Determine whether the next list item belongs here.
233
- // Backpedal if it does not belong in this list.
234
- if (i !== l - 1) {
235
- b = block.bullet.exec(cap[i + 1])[0];
236
- if (bull.length > 1 ? b.length === 1
237
- : (b.length > 1 || (this.options.smartLists && b !== bull))) {
238
- src = cap.slice(i + 1).join('\n') + src;
239
- i = l - 1;
240
- }
241
- }
220
+ // top-level paragraph
221
+ if (top && (token = this.tokenizer.paragraph(src))) {
222
+ src = src.substring(token.raw.length);
223
+ tokens.push(token);
224
+ continue;
225
+ }
242
226
 
243
- // Determine whether item is loose or not.
244
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
245
- // for discount behavior.
246
- loose = next || /\n\n(?!\s*$)/.test(item);
247
- if (i !== l - 1) {
248
- next = item.charAt(item.length - 1) === '\n';
249
- if (!loose) loose = next;
250
- }
227
+ // text
228
+ if (token = this.tokenizer.text(src, tokens)) {
229
+ src = src.substring(token.raw.length);
230
+ if (token.type) {
231
+ tokens.push(token);
232
+ } else {
233
+ lastToken = tokens[tokens.length - 1];
234
+ lastToken.raw += '\n' + token.raw;
235
+ lastToken.text += '\n' + token.text;
236
+ }
237
+ continue;
238
+ }
251
239
 
252
- if (loose) {
253
- listStart.loose = true;
254
- }
240
+ if (src) {
241
+ const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
242
+ if (this.options.silent) {
243
+ console.error(errMsg);
244
+ break;
245
+ } else {
246
+ throw new Error(errMsg);
247
+ }
248
+ }
249
+ }
255
250
 
256
- // Check for task list items
257
- istask = /^\[[ xX]\] /.test(item);
258
- ischecked = undefined;
259
- if (istask) {
260
- ischecked = item[1] !== ' ';
261
- item = item.replace(/^\[[ xX]\] +/, '');
262
- }
251
+ return tokens;
252
+ }
263
253
 
264
- t = {
265
- type: 'list_item_start',
266
- task: istask,
267
- checked: ischecked,
268
- loose: loose
254
+ inline(tokens) {
255
+ let i,
256
+ j,
257
+ k,
258
+ l2,
259
+ row,
260
+ token;
261
+
262
+ const l = tokens.length;
263
+ for (i = 0; i < l; i++) {
264
+ token = tokens[i];
265
+ switch (token.type) {
266
+ case 'paragraph':
267
+ case 'text':
268
+ case 'heading': {
269
+ token.tokens = [];
270
+ this.inlineTokens(token.text, token.tokens);
271
+ break;
272
+ }
273
+ case 'table': {
274
+ token.tokens = {
275
+ header: [],
276
+ cells: []
269
277
  };
270
278
 
271
- listItems.push(t);
272
- this.tokens.push(t);
279
+ // header
280
+ l2 = token.header.length;
281
+ for (j = 0; j < l2; j++) {
282
+ token.tokens.header[j] = [];
283
+ this.inlineTokens(token.header[j], token.tokens.header[j]);
284
+ }
273
285
 
274
- // Recurse.
275
- this.token(item, false);
286
+ // cells
287
+ l2 = token.cells.length;
288
+ for (j = 0; j < l2; j++) {
289
+ row = token.cells[j];
290
+ token.tokens.cells[j] = [];
291
+ for (k = 0; k < row.length; k++) {
292
+ token.tokens.cells[j][k] = [];
293
+ this.inlineTokens(row[k], token.tokens.cells[j][k]);
294
+ }
295
+ }
276
296
 
277
- this.tokens.push({
278
- type: 'list_item_end'
279
- });
297
+ break;
298
+ }
299
+ case 'blockquote': {
300
+ this.inline(token.tokens);
301
+ break;
280
302
  }
303
+ case 'list': {
304
+ l2 = token.items.length;
305
+ for (j = 0; j < l2; j++) {
306
+ this.inline(token.items[j].tokens);
307
+ }
308
+ break;
309
+ }
310
+ default: {
311
+ // do nothing
312
+ }
313
+ }
314
+ }
315
+
316
+ return tokens;
317
+ }
281
318
 
282
- if (listStart.loose) {
283
- l = listItems.length;
284
- i = 0;
285
- for (; i < l; i++) {
286
- listItems[i].loose = true;
319
+ /**
320
+ * Lexing/Compiling
321
+ */
322
+ inlineTokens(src, tokens = [], inLink = false, inRawBlock = false, prevChar = '') {
323
+ let token;
324
+
325
+ // String with links masked to avoid interference with em and strong
326
+ let maskedSrc = src;
327
+ let match;
328
+
329
+ // Mask out reflinks
330
+ if (this.tokens.links) {
331
+ const links = Object.keys(this.tokens.links);
332
+ if (links.length > 0) {
333
+ while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
334
+ if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
335
+ maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
287
336
  }
288
337
  }
338
+ }
339
+ }
340
+ // Mask out other blocks
341
+ while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
342
+ maskedSrc = maskedSrc.slice(0, match.index) + '[' + 'a'.repeat(match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
343
+ }
289
344
 
290
- this.tokens.push({
291
- type: 'list_end'
292
- });
345
+ while (src) {
346
+ // escape
347
+ if (token = this.tokenizer.escape(src)) {
348
+ src = src.substring(token.raw.length);
349
+ tokens.push(token);
350
+ continue;
351
+ }
293
352
 
353
+ // tag
354
+ if (token = this.tokenizer.tag(src, inLink, inRawBlock)) {
355
+ src = src.substring(token.raw.length);
356
+ inLink = token.inLink;
357
+ inRawBlock = token.inRawBlock;
358
+ tokens.push(token);
294
359
  continue;
295
360
  }
296
361
 
297
- // html
298
- if (cap = this.rules.html.exec(src)) {
299
- src = src.substring(cap[0].length);
300
- this.tokens.push({
301
- type: this.options.sanitize
302
- ? 'paragraph'
303
- : 'html',
304
- pre: !this.options.sanitizer
305
- && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
306
- text: this.options.sanitize ? (this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0])) : cap[0]
307
- });
362
+ // link
363
+ if (token = this.tokenizer.link(src)) {
364
+ src = src.substring(token.raw.length);
365
+ if (token.type === 'link') {
366
+ token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
367
+ }
368
+ tokens.push(token);
308
369
  continue;
309
370
  }
310
371
 
311
- // def
312
- if (top && (cap = this.rules.def.exec(src))) {
313
- src = src.substring(cap[0].length);
314
- if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
315
- tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
316
- if (!this.tokens.links[tag]) {
317
- this.tokens.links[tag] = {
318
- href: cap[2],
319
- title: cap[3]
320
- };
372
+ // reflink, nolink
373
+ if (token = this.tokenizer.reflink(src, this.tokens.links)) {
374
+ src = src.substring(token.raw.length);
375
+ if (token.type === 'link') {
376
+ token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
321
377
  }
378
+ tokens.push(token);
322
379
  continue;
323
380
  }
324
381
 
325
- // table (gfm)
326
- if (cap = this.rules.table.exec(src)) {
327
- item = {
328
- type: 'table',
329
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
330
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
331
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
332
- };
333
-
334
- if (item.header.length === item.align.length) {
335
- src = src.substring(cap[0].length);
336
-
337
- for (i = 0; i < item.align.length; i++) {
338
- if (/^ *-+: *$/.test(item.align[i])) {
339
- item.align[i] = 'right';
340
- } else if (/^ *:-+: *$/.test(item.align[i])) {
341
- item.align[i] = 'center';
342
- } else if (/^ *:-+ *$/.test(item.align[i])) {
343
- item.align[i] = 'left';
344
- } else {
345
- item.align[i] = null;
346
- }
347
- }
382
+ // strong
383
+ if (token = this.tokenizer.strong(src, maskedSrc, prevChar)) {
384
+ src = src.substring(token.raw.length);
385
+ token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
386
+ tokens.push(token);
387
+ continue;
388
+ }
348
389
 
349
- for (i = 0; i < item.cells.length; i++) {
350
- item.cells[i] = splitCells(
351
- item.cells[i].replace(/^ *\| *| *\| *$/g, ''),
352
- item.header.length);
353
- }
390
+ // em
391
+ if (token = this.tokenizer.em(src, maskedSrc, prevChar)) {
392
+ src = src.substring(token.raw.length);
393
+ token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
394
+ tokens.push(token);
395
+ continue;
396
+ }
354
397
 
355
- this.tokens.push(item);
398
+ // code
399
+ if (token = this.tokenizer.codespan(src)) {
400
+ src = src.substring(token.raw.length);
401
+ tokens.push(token);
402
+ continue;
403
+ }
356
404
 
357
- continue;
358
- }
405
+ // br
406
+ if (token = this.tokenizer.br(src)) {
407
+ src = src.substring(token.raw.length);
408
+ tokens.push(token);
409
+ continue;
359
410
  }
360
411
 
361
- // lheading
362
- if (cap = this.rules.lheading.exec(src)) {
363
- src = src.substring(cap[0].length);
364
- this.tokens.push({
365
- type: 'heading',
366
- depth: cap[2].charAt(0) === '=' ? 1 : 2,
367
- text: cap[1]
368
- });
412
+ // del (gfm)
413
+ if (token = this.tokenizer.del(src)) {
414
+ src = src.substring(token.raw.length);
415
+ token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
416
+ tokens.push(token);
369
417
  continue;
370
418
  }
371
419
 
372
- // top-level paragraph
373
- if (top && (cap = this.rules.paragraph.exec(src))) {
374
- src = src.substring(cap[0].length);
375
- this.tokens.push({
376
- type: 'paragraph',
377
- text: cap[1].charAt(cap[1].length - 1) === '\n'
378
- ? cap[1].slice(0, -1)
379
- : cap[1]
380
- });
420
+ // autolink
421
+ if (token = this.tokenizer.autolink(src, mangle)) {
422
+ src = src.substring(token.raw.length);
423
+ tokens.push(token);
424
+ continue;
425
+ }
426
+
427
+ // url (gfm)
428
+ if (!inLink && (token = this.tokenizer.url(src, mangle))) {
429
+ src = src.substring(token.raw.length);
430
+ tokens.push(token);
381
431
  continue;
382
432
  }
383
433
 
384
434
  // text
385
- if (cap = this.rules.text.exec(src)) {
386
- // Top-level should never reach here.
387
- src = src.substring(cap[0].length);
388
- this.tokens.push({
389
- type: 'text',
390
- text: cap[0]
391
- });
435
+ if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
436
+ src = src.substring(token.raw.length);
437
+ prevChar = token.raw.slice(-1);
438
+ tokens.push(token);
392
439
  continue;
393
440
  }
394
441
 
395
442
  if (src) {
396
- throw new Error('Infinite loop on byte: ' + src.charCodeAt(0));
443
+ const errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
444
+ if (this.options.silent) {
445
+ console.error(errMsg);
446
+ break;
447
+ } else {
448
+ throw new Error(errMsg);
449
+ }
397
450
  }
398
451
  }
399
452
 
400
- return this.tokens;
401
- };
453
+ return tokens;
454
+ }
402
455
  };