From b5a50041ae0347a80d8d017b39b46a57f58354d0 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Tue, 6 Aug 2024 21:31:12 -0600 Subject: [PATCH] fix: allow async option to dictate type returned (#3341) BREAKING CHANGE: throw an error if `async: false` is set when an extension sets `async: true` --- src/Instance.ts | 31 ++++++++++++++++++------------- src/marked.ts | 6 ++++-- test/types/marked.ts | 2 -- test/unit/marked.test.js | 4 ++-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/Instance.ts b/src/Instance.ts index 16e2c060e8..7aad1daaca 100644 --- a/src/Instance.ts +++ b/src/Instance.ts @@ -21,8 +21,8 @@ export class Marked { defaults = _getDefaults(); options = this.setOptions; - parse = this.#parseMarkdown(_Lexer.lex, _Parser.parse); - parseInline = this.#parseMarkdown(_Lexer.lexInline, _Parser.parseInline); + parse = this.parseMarkdown(_Lexer.lex, _Parser.parse); + parseInline = this.parseMarkdown(_Lexer.lexInline, _Parser.parseInline); Parser = _Parser; Renderer = _Renderer; @@ -514,22 +514,25 @@ export class Marked { return _Parser.parse(tokens, options ?? this.defaults); } - #parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) { - return (src: string, options?: MarkedOptions | undefined | null): string | Promise => { + private parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) { + type overloadedParse = { + (src: string, options: MarkedOptions & { async: true }): Promise; + (src: string, options: MarkedOptions & { async: false }): string; + (src: string, options?: MarkedOptions | undefined | null): string | Promise; + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parse: overloadedParse = (src: string, options?: MarkedOptions | undefined | null): any => { const origOpt = { ...options }; const opt = { ...this.defaults, ...origOpt }; - // Show warning if an extension set async to true but the parse was called with async: false - if (this.defaults.async === true && origOpt.async === false) { - if (!opt.silent) { - console.warn('marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored.'); - } + const throwError = this.onError(!!opt.silent, !!opt.async); - opt.async = true; + // throw error if an extension set async to true but parse was called with async: false + if (this.defaults.async === true && origOpt.async === false) { + return throwError(new Error('marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.')); } - const throwError = this.#onError(!!opt.silent, !!opt.async); - // throw error in case of non string input if (typeof src === 'undefined' || src === null) { return throwError(new Error('marked(): input parameter is undefined or null')); @@ -573,9 +576,11 @@ export class Marked { return throwError(e as Error); } }; + + return parse; } - #onError(silent: boolean, async: boolean) { + private onError(silent: boolean, async: boolean) { return (e: Error): string | Promise => { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; diff --git a/src/marked.ts b/src/marked.ts index ac83640ae5..7ec18f5ae6 100644 --- a/src/marked.ts +++ b/src/marked.ts @@ -32,8 +32,10 @@ export function marked(src: string, options: MarkedOptions & { async: true }): P * @param options Optional hash of options * @return String of compiled HTML. Will be a Promise of string if async is set to true by any extensions. */ -export function marked(src: string, options?: MarkedOptions): string | Promise; -export function marked(src: string, opt?: MarkedOptions): string | Promise { +export function marked(src: string, options: MarkedOptions & { async: false }): string; +export function marked(src: string, options: MarkedOptions & { async: true }): Promise; +export function marked(src: string, options?: MarkedOptions | undefined | null): string | Promise; +export function marked(src: string, opt?: MarkedOptions | undefined | null): string | Promise { return markedInstance.parse(src, opt); } diff --git a/test/types/marked.ts b/test/types/marked.ts index 607fbf50c8..bf7a3e9433 100644 --- a/test/types/marked.ts +++ b/test/types/marked.ts @@ -261,7 +261,6 @@ marked.use(asyncExtension); const md = '# foobar'; const asyncMarked: string = await marked(md, { async: true }); const promiseMarked: Promise = marked(md, { async: true }); -// @ts-expect-error marked can still be async if an extension sets `async: true` const notAsyncMarked: string = marked(md, { async: false }); // @ts-expect-error marked can still be async if an extension sets `async: true` const defaultMarked: string = marked(md); @@ -270,7 +269,6 @@ const stringMarked: string = marked(md) as string; const asyncMarkedParse: string = await marked.parse(md, { async: true }); const promiseMarkedParse: Promise = marked.parse(md, { async: true }); -// @ts-expect-error marked can still be async if an extension sets `async: true` const notAsyncMarkedParse: string = marked.parse(md, { async: false }); // @ts-expect-error marked can still be async if an extension sets `async: true` const defaultMarkedParse: string = marked.parse(md); diff --git a/test/unit/marked.test.js b/test/unit/marked.test.js index 6d2854f299..87931c86a0 100644 --- a/test/unit/marked.test.js +++ b/test/unit/marked.test.js @@ -630,10 +630,10 @@ used extension2 walked

assert.strictEqual(typeof marked.parse('test', { async: false }), 'string'); }); - it('should return Promise if async is set by extension', () => { + it('should throw an error if async is set by extension', () => { marked.use({ async: true }); - assert.ok(marked.parse('test', { async: false }) instanceof Promise); + assert.throws(() => marked.parse('test', { async: false })); }); it('should allow deleting/editing tokens', () => {