From f2b221945f26645c857b19083b144df71a140b6f Mon Sep 17 00:00:00 2001 From: Yimiprod Date: Fri, 1 Dec 2023 13:17:11 +0100 Subject: [PATCH] fix: marked and parse type overload with discrinated union options --- src/Instance.ts | 15 ++++++++++----- src/MarkedOptions.ts | 20 +++++++++++++++++++- src/marked.ts | 14 +++++++++++--- test/types/marked.ts | 23 +++++++++++++++-------- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/Instance.ts b/src/Instance.ts index 2ca489f297..895d909ae4 100644 --- a/src/Instance.ts +++ b/src/Instance.ts @@ -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; @@ -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 => { - 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.'); } @@ -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) diff --git a/src/MarkedOptions.ts b/src/MarkedOptions.ts index e4370501ef..9e0cfc648f 100644 --- a/src/MarkedOptions.ts +++ b/src/MarkedOptions.ts @@ -109,7 +109,7 @@ export interface MarkedExtension { walkTokens?: ((token: Token) => void | Promise) | undefined | null; } -export interface MarkedOptions extends Omit { +interface _MarkedOptions extends Omit { /** * Type: object Default: new Renderer() * @@ -143,3 +143,21 @@ export interface MarkedOptions extends Omit void | Promise | (void | Promise)[]); } + +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); +} diff --git a/src/marked.ts b/src/marked.ts index 9301db095c..ccb593ce98 100644 --- a/src/marked.ts +++ b/src/marked.ts @@ -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'; @@ -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; - +export function marked(src: string, options: MarkedAsyncOptions): Promise; +/** + * 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. * @@ -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'; diff --git a/test/types/marked.ts b/test/types/marked.ts index 571edaf789..2eaf71a6bd 100644 --- a/test/types/marked.ts +++ b/test/types/marked.ts @@ -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'; @@ -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); @@ -246,21 +261,13 @@ 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); -// 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 = 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