diff --git a/src/marked.js b/src/marked.js index f9f59adc..c5bbca4e 100644 --- a/src/marked.js +++ b/src/marked.js @@ -72,8 +72,10 @@ function marked(src, opt, callback) { if (!tokens.length) return done(); + let pending = 0; marked.iterateTokens(tokens, function(token) { if (token.type === 'code') { + pending++; highlight(token.text, token.lang, function(err, code) { if (err) { return done(err); @@ -82,11 +84,20 @@ function marked(src, opt, callback) { token.text = code; token.escaped = true; } + + pending--; + if (pending === 0) { + done(); + } }); } }); - return done(); + if (pending === 0) { + done(); + } + + return; } try { @@ -167,16 +178,20 @@ marked.iterateTokens = function(tokens, callback) { } switch (token.type) { case 'table': { - ret = marked.iterateTokens(token.tokens.header, callback); - if (ret === false) { - return false; - } - for (const row of token.tokens.cell) { - ret = marked.iterateTokens(row, callback); + for (const cell of token.tokens.header) { + ret = marked.iterateTokens(cell, callback); if (ret === false) { return false; } } + for (const row of token.tokens.cells) { + for (const cell of row) { + ret = marked.iterateTokens(cell, callback); + if (ret === false) { + return false; + } + } + } break; } case 'list': { diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js index 1b6beafd..3087f46f 100644 --- a/test/unit/marked-spec.js +++ b/test/unit/marked-spec.js @@ -229,3 +229,167 @@ paragraph expect(html).toBe('arrow no options\nfunction options\nshorthand options\n'); }); }); + +describe('async highlight', () => { + let highlight, markdown; + beforeEach(() => { + highlight = jasmine.createSpy('highlight', (text, lang, callback) => { + setImmediate(() => { + callback(null, `async ${text || ''}`); + }); + }); + markdown = ` +\`\`\`lang1 +text 1 +\`\`\` + +> \`\`\`lang2 +> text 2 +> \`\`\` + +- \`\`\`lang3 + text 3 + \`\`\` +`; + }); + + it('should highlight codeblocks async', (done) => { + highlight.and.callThrough(); + + marked(markdown, { highlight }, (err, html) => { + if (err) { + fail(err); + } + + expect(html).toBe(`
async text 1
+
+
async text 2
+
+ +`); + done(); + }); + }); + + it('should call callback for each error in highlight', (done) => { + highlight.and.callFake((lang, text, callback) => { + callback(new Error('highlight error')); + }); + + let numErrors = 0; + marked(markdown, { highlight }, (err, html) => { + expect(err).toBeTruthy(); + expect(html).toBeUndefined(); + + if (err) { + numErrors++; + } + + if (numErrors === 3) { + done(); + } + }); + }); +}); + +describe('iterateTokens', () => { + it('should iterate over every token', () => { + const markdown = ` +paragraph + +--- + +# heading + +\`\`\` +code +\`\`\` + +| a | b | +|---|---| +| 1 | 2 | +| 3 | 4 | + +> blockquote + +- list + +
html
+ +[link](https://example.com) + +![image](https://example.com/image.jpg) + +**strong** + +*em* + +\`codespan\` + +~~del~~ + +br +br +`; + const tokens = marked.lexer(markdown, { ...marked.getDefaults(), breaks: true }); + const tokensSeen = []; + marked.iterateTokens(tokens, (token) => { + tokensSeen.push([token.type, (token.raw || '').replace(/\n/g, '')]); + }); + + expect(tokensSeen).toEqual([ + ['paragraph', 'paragraph'], + ['text', 'paragraph'], + ['space', ''], + ['hr', '---'], + ['heading', '# heading'], + ['text', 'heading'], + ['code', '```code```'], + ['table', '| a | b ||---|---|| 1 | 2 || 3 | 4 |'], + ['text', 'a'], + ['text', 'b'], + ['text', '1'], + ['text', '2'], + ['text', '3'], + ['text', '4'], + ['blockquote', '> blockquote'], + ['paragraph', 'blockquote'], + ['text', 'blockquote'], + ['list', '- list'], + ['list_item', '- list'], + ['text', 'list'], + ['text', 'list'], + ['space', ''], + ['html', '
html
'], + ['paragraph', '[link](https://example.com)'], + ['link', '[link](https://example.com)'], + ['text', 'link'], + ['space', ''], + ['paragraph', '![image](https://example.com/image.jpg)'], + ['image', '![image](https://example.com/image.jpg)'], + ['space', ''], + ['paragraph', '**strong**'], + ['strong', '**strong**'], + ['text', 'strong'], + ['space', ''], + ['paragraph', '*em*'], + ['em', '*em*'], + ['text', 'em'], + ['space', ''], + ['paragraph', '`codespan`'], + ['codespan', '`codespan`'], + ['space', ''], + ['paragraph', '~~del~~'], + ['del', '~~del~~'], + ['text', 'del'], + ['space', ''], + ['paragraph', 'brbr'], + ['text', 'br'], + ['br', ''], + ['text', 'br'] + ]); + }); +});