Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ export class _Hooks<ParserOutput = string, RendererOutput = string> {
/**
* Provide function to tokenize markdown
*/
provideLexer() {
return this.block ? _Lexer.lex : _Lexer.lexInline;
provideLexer(block = this.block) {
return block ? _Lexer.lex : _Lexer.lexInline;
}

/**
* Provide function to parse tokens
*/
provideParser() {
return this.block ? _Parser.parse<ParserOutput, RendererOutput> : _Parser.parseInline<ParserOutput, RendererOutput>;
provideParser(block = this.block) {
return block ? _Parser.parse<ParserOutput, RendererOutput> : _Parser.parseInline<ParserOutput, RendererOutput>;
}
}
8 changes: 4 additions & 4 deletions src/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,13 @@ export class Marked<ParserOutput = string, RendererOutput = string> {
if (opt.async) {
return (async() => {
const processedSrc = opt.hooks ? await opt.hooks.preprocess(src) : src;
const lexer = opt.hooks ? await opt.hooks.provideLexer() : (blockType ? _Lexer.lex : _Lexer.lexInline);
const lexer = opt.hooks ? await opt.hooks.provideLexer(blockType) : (blockType ? _Lexer.lex : _Lexer.lexInline);
const tokens = await lexer(processedSrc, opt);
const processedTokens = opt.hooks ? await opt.hooks.processAllTokens(tokens) : tokens;
if (opt.walkTokens) {
await Promise.all(this.walkTokens(processedTokens, opt.walkTokens));
}
const parser = opt.hooks ? await opt.hooks.provideParser() : (blockType ? _Parser.parse : _Parser.parseInline);
const parser = opt.hooks ? await opt.hooks.provideParser(blockType) : (blockType ? _Parser.parse : _Parser.parseInline);
const html = await parser(processedTokens, opt);
return opt.hooks ? await opt.hooks.postprocess(html) : html;
})().catch(throwError);
Expand All @@ -324,15 +324,15 @@ export class Marked<ParserOutput = string, RendererOutput = string> {
if (opt.hooks) {
src = opt.hooks.preprocess(src) as string;
}
const lexer = opt.hooks ? opt.hooks.provideLexer() : (blockType ? _Lexer.lex : _Lexer.lexInline);
const lexer = opt.hooks ? opt.hooks.provideLexer(blockType) : (blockType ? _Lexer.lex : _Lexer.lexInline);
let tokens = lexer(src, opt);
if (opt.hooks) {
tokens = opt.hooks.processAllTokens(tokens);
}
if (opt.walkTokens) {
this.walkTokens(tokens, opt.walkTokens);
}
const parser = opt.hooks ? opt.hooks.provideParser() : (blockType ? _Parser.parse : _Parser.parseInline);
const parser = opt.hooks ? opt.hooks.provideParser(blockType) : (blockType ? _Parser.parse : _Parser.parseInline);
let html = parser(tokens, opt);
if (opt.hooks) {
html = opt.hooks.postprocess(html);
Expand Down
194 changes: 194 additions & 0 deletions test/unit/Hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,4 +324,198 @@ describe('Hooks', () => {
const html = await marked.parse('text');
assert.strictEqual(html.trim(), 'test parser');
});

it('should not have race condition when parse and parseInline are called concurrently with async hooks', async() => {
marked.use({
async: true,
hooks: {
async preprocess(markdown) {
await timeout();
return markdown;
},
},
});
const [blockHtml, inlineHtml] = await Promise.all([
marked.parse('**text**'),
marked.parseInline('**text**'),
]);
assert.strictEqual(blockHtml.trim(), '<p><strong>text</strong></p>');
assert.strictEqual(inlineHtml.trim(), '<strong>text</strong>');
});

it('should not have race condition with multiple concurrent parse calls', async() => {
marked.use({
async: true,
hooks: {
async preprocess(markdown) {
await timeout();
return markdown;
},
},
});
const [html1, html2, html3] = await Promise.all([
marked.parse('**bold**'),
marked.parseInline('**bold**'),
marked.parse('*italic*'),
]);
assert.strictEqual(html1.trim(), '<p><strong>bold</strong></p>');
assert.strictEqual(html2.trim(), '<strong>bold</strong>');
assert.strictEqual(html3.trim(), '<p><em>italic</em></p>');
});

it('should pass block=true to provideLexer when called from parse', () => {
let receivedBlock;
marked.use({
hooks: {
provideLexer(block) {
receivedBlock = block;
return () => [];
},
},
});
marked.parse('text');
assert.strictEqual(receivedBlock, true);
});

it('should pass block=false to provideLexer when called from parseInline', () => {
let receivedBlock;
marked.use({
hooks: {
provideLexer(block) {
receivedBlock = block;
return () => [];
},
},
});
marked.parseInline('text');
assert.strictEqual(receivedBlock, false);
});

it('should pass correct block to provideLexer for concurrent async parse and parseInline', async() => {
const receivedBlocks = [];
marked.use({
async: true,
hooks: {
async preprocess(markdown) {
await timeout();
return markdown;
},
provideLexer(block) {
receivedBlocks.push(block);
return () => [];
},
},
});
await Promise.all([
marked.parse('text'),
marked.parseInline('text'),
]);
assert.deepStrictEqual(receivedBlocks.slice().sort(), [false, true]);
});

it('should pass block=true to provideParser when called from parse', () => {
let receivedBlock;
marked.use({
hooks: {
provideParser(block) {
receivedBlock = block;
return () => '';
},
},
});
marked.parse('text');
assert.strictEqual(receivedBlock, true);
});

it('should pass block=false to provideParser when called from parseInline', () => {
let receivedBlock;
marked.use({
hooks: {
provideParser(block) {
receivedBlock = block;
return () => '';
},
},
});
marked.parseInline('text');
assert.strictEqual(receivedBlock, false);
});

it('should pass correct block to provideParser for concurrent async parse and parseInline', async() => {
const receivedBlocks = [];
marked.use({
async: true,
hooks: {
async preprocess(markdown) {
await timeout();
return markdown;
},
provideParser(block) {
receivedBlocks.push(block);
return () => '';
},
},
});
await Promise.all([
marked.parse('text'),
marked.parseInline('text'),
]);
assert.deepStrictEqual(receivedBlocks.slice().sort(), [false, true]);
});

it('should maintain this.block backwards compatibility in provideLexer for parse', () => {
let blockFromThis;
marked.use({
hooks: {
provideLexer() {
blockFromThis = this.block;
return () => [];
},
},
});
marked.parse('text');
assert.strictEqual(blockFromThis, true);
});

it('should maintain this.block backwards compatibility in provideLexer for parseInline', () => {
let blockFromThis;
marked.use({
hooks: {
provideLexer() {
blockFromThis = this.block;
return () => [];
},
},
});
marked.parseInline('text');
assert.strictEqual(blockFromThis, false);
});

it('should maintain this.block backwards compatibility in provideParser for parse', () => {
let blockFromThis;
marked.use({
hooks: {
provideParser() {
blockFromThis = this.block;
return () => '';
},
},
});
marked.parse('text');
assert.strictEqual(blockFromThis, true);
});

it('should maintain this.block backwards compatibility in provideParser for parseInline', () => {
let blockFromThis;
marked.use({
hooks: {
provideParser() {
blockFromThis = this.block;
return () => '';
},
},
});
marked.parseInline('text');
assert.strictEqual(blockFromThis, false);
});
});