Skip to content

Commit

Permalink
fix: marked and parse type overload with discrinated union options
Browse files Browse the repository at this point in the history
  • Loading branch information
Yimiprod committed Dec 1, 2023
1 parent f45cfc3 commit f2b2219
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 17 deletions.
15 changes: 10 additions & 5 deletions src/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { _TextRenderer } from './TextRenderer.ts';
import {
escape
} from './helpers.ts';
import type { MarkedExtension, MarkedOptions } from './MarkedOptions.ts';
import {
isAsyncOptions,
isSyncOptions,
type MarkedExtension,
type MarkedOptions
} from './MarkedOptions.ts';
import type { Token, Tokens, TokensList } from './Tokens.ts';

export type MaybePromise = void | Promise<void>;
Expand Down Expand Up @@ -250,11 +255,11 @@ export class Marked {

#parseMarkdown(lexer: (src: string, options?: MarkedOptions) => TokensList | Token[], parser: (tokens: Token[], options?: MarkedOptions) => string) {
return (src: string, options?: MarkedOptions | undefined | null): string | Promise<string> => {
const origOpt = { ...options };
const opt = { ...this.defaults, ...origOpt };
const origOpt: MarkedOptions = { ...options };
const opt: MarkedOptions = { ...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 (isAsyncOptions(this.defaults) && isSyncOptions(origOpt)) {
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.');
}
Expand All @@ -277,7 +282,7 @@ export class Marked {
opt.hooks.options = opt;
}

if (opt.async) {
if (isSyncOptions(opt)) {
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src)
.then(src => lexer(src, opt))
.then(tokens => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens)
Expand Down
20 changes: 19 additions & 1 deletion src/MarkedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export interface MarkedExtension {
walkTokens?: ((token: Token) => void | Promise<void>) | undefined | null;
}

export interface MarkedOptions extends Omit<MarkedExtension, 'renderer' | 'tokenizer' | 'extensions' | 'walkTokens'> {
interface _MarkedOptions extends Omit<MarkedExtension, 'renderer' | 'tokenizer' | 'extensions' | 'walkTokens'> {
/**
* Type: object Default: new Renderer()
*
Expand Down Expand Up @@ -143,3 +143,21 @@ export interface MarkedOptions extends Omit<MarkedExtension, 'renderer' | 'token
*/
walkTokens?: null | ((token: Token) => void | Promise<void> | (void | Promise<void>)[]);
}

export interface MarkedSyncOptions extends _MarkedOptions {
async?: false;
}

export interface MarkedAsyncOptions extends _MarkedOptions {
async: true;
}

export type MarkedOptions = MarkedSyncOptions | MarkedAsyncOptions;

export function isAsyncOptions(options: MarkedOptions): options is MarkedAsyncOptions {
return 'async' in options && options.async === true;
}

export function isSyncOptions(options: MarkedOptions): options is MarkedSyncOptions {
return !isAsyncOptions(options);
}
14 changes: 11 additions & 3 deletions src/marked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
changeDefaults,
_defaults
} from './defaults.ts';
import type { MarkedExtension, MarkedOptions } from './MarkedOptions.ts';
import { isAsyncOptions, isSyncOptions, type MarkedAsyncOptions, type MarkedExtension, type MarkedOptions, type MarkedSyncOptions } from './MarkedOptions.ts';
import type { Token, TokensList } from './Tokens.ts';
import type { MaybePromise } from './Instance.ts';

Expand All @@ -23,8 +23,15 @@ const markedInstance = new Marked();
* @param options Hash of options, having async: true
* @return Promise of string of compiled HTML
*/
export function marked(src: string, options: MarkedOptions & { async: true }): Promise<string>;

export function marked(src: string, options: MarkedAsyncOptions): Promise<string>;
/**
* Compiles markdown to HTML synchronously.
*
* @param src String of markdown source to be compiled
* @param options Hash of options, having async: false or undefined
* @return String of compiled HTML
*/
export function marked(src: string, options?: MarkedSyncOptions): string;
/**
* Compiles markdown to HTML.
*
Expand Down Expand Up @@ -115,5 +122,6 @@ export { _TextRenderer as TextRenderer } from './TextRenderer.ts';
export { _Hooks as Hooks } from './Hooks.ts';
export { Marked } from './Instance.ts';
export type * from './MarkedOptions.ts';
export { isSyncOptions, isAsyncOptions } from './MarkedOptions.ts';
export type * from './rules.ts';
export type * from './Tokens.ts';
23 changes: 15 additions & 8 deletions test/types/marked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { marked } from 'marked';

// other exports

import { isAsyncOptions, isSyncOptions } from 'marked';
import { Lexer, Parser, Tokenizer, Renderer, TextRenderer } from 'marked';
import type { Tokens, MarkedExtension, TokenizerAndRendererExtension, Token ,TokenizerExtension, MarkedOptions, TokensList, RendererExtension } from 'marked';

Expand Down Expand Up @@ -89,6 +90,20 @@ renderer.checkbox = checked => {
return checked ? 'CHECKED' : 'UNCHECKED';
};

options = {...options, async: false};

if (isSyncOptions(options)) {
console.log(await marked.parseInline('12) I am using __markdown__.', options));
}

options = {...options, async: true};

if (isAsyncOptions(options)) {
(async () => {
console.log(await marked.parseInline('12) I am using __markdown__.', options));
})()
}

class ExtendedRenderer extends marked.Renderer {
code = (code: string, language: string | undefined, isEscaped: boolean): string => super.code(code, language, isEscaped);
blockquote = (quote: string): string => super.blockquote(quote);
Expand Down Expand Up @@ -246,21 +261,13 @@ 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);
// as string can be used if no extensions set `async: true`
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);
// as string can be used if no extensions set `async: true`
const stringMarkedParse: string = marked.parse(md) as string;
})();

// Tests for List and ListItem
Expand Down

0 comments on commit f2b2219

Please sign in to comment.