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`
This commit is contained in:
Tony Brix 2024-08-06 21:31:12 -06:00 committed by GitHub
parent ead7af34a5
commit b5a50041ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 19 deletions

View File

@ -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<string> => {
private parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) {
type overloadedParse = {
(src: string, options: MarkedOptions & { async: true }): Promise<string>;
(src: string, options: MarkedOptions & { async: false }): string;
(src: string, options?: MarkedOptions | undefined | null): string | Promise<string>;
};
// 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
const throwError = this.onError(!!opt.silent, !!opt.async);
// throw error if an extension set async to true but 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.');
}
opt.async = true;
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<string> => {
e.message += '\nPlease report this to https://github.com/markedjs/marked.';

View File

@ -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<string>;
export function marked(src: string, opt?: MarkedOptions): string | Promise<string> {
export function marked(src: string, options: MarkedOptions & { async: false }): string;
export function marked(src: string, options: MarkedOptions & { async: true }): Promise<string>;
export function marked(src: string, options?: MarkedOptions | undefined | null): string | Promise<string>;
export function marked(src: string, opt?: MarkedOptions | undefined | null): string | Promise<string> {
return markedInstance.parse(src, opt);
}

View File

@ -261,7 +261,6 @@ marked.use(asyncExtension);
const md = '# foobar';
const asyncMarked: string = await marked(md, { async: true });
const promiseMarked: Promise<string> = 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<string> = 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);

View File

@ -630,10 +630,10 @@ used extension2 walked</p>
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', () => {