Skip to content

Commit

Permalink
fix: allow async option to dictate type returned (#3341)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: throw an error if `async: false` is set when an extension sets `async: true`
  • Loading branch information
UziTech authored Aug 7, 2024
1 parent ead7af3 commit b5a5004
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 19 deletions.
31 changes: 18 additions & 13 deletions src/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
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'));
Expand Down Expand Up @@ -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.';

Expand Down
6 changes: 4 additions & 2 deletions src/marked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 0 additions & 2 deletions test/types/marked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions test/unit/marked.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down

0 comments on commit b5a5004

Please sign in to comment.