/
githubmirror
/
marked
Обзор
Документация
Войти
/
githubmirror
/
marked
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/Instance.ts
391 строка
15 KB
Columbus Labs
docs: explain partial extensions in use (#4033)
04 авг 2026, 07:32
Не верифицирован
04 авг 2026, 07:32
641e5df
Код
Авторство
О чём код?
import { _getDefaults } from './defaults.ts'; import { _Lexer } from './Lexer.ts'; import { _Parser } from './Parser.ts'; import { _Hooks } from './Hooks.ts'; import { _Renderer } from './Renderer.ts'; import { _Tokenizer } from './Tokenizer.ts'; import { _TextRenderer } from './TextRenderer.ts'; import { escapeHtmlEntities } from './helpers.ts'; import type { MarkedExtension, MarkedOptions } from './MarkedOptions.ts'; import type { Token, Tokens, TokensList } from './Tokens.ts'; export type MaybePromise = void | Promise<void>; type UnknownFunction = (...args: unknown[]) => unknown; type GenericRendererFunction = (...args: unknown[]) => string | false; export class Marked<ParserOutput = string, RendererOutput = string> { defaults = _getDefaults<ParserOutput, RendererOutput>(); options = this.setOptions; /** * Compiles markdown to HTML. * * To configure hooks, a renderer, or a tokenizer, create a separate `Marked` * instance and use `Marked#use` instead of passing them on each call. */ parse = this.parseMarkdown(true); /** * Compiles inline markdown to HTML. * * To configure hooks, a renderer, or a tokenizer, create a separate `Marked` * instance and use `Marked#use` instead of passing them on each call. */ parseInline = this.parseMarkdown(false); Parser = _Parser<ParserOutput, RendererOutput>; Renderer = _Renderer<ParserOutput, RendererOutput>; TextRenderer = _TextRenderer<RendererOutput>; Lexer = _Lexer; Tokenizer = _Tokenizer<ParserOutput, RendererOutput>; Hooks = _Hooks<ParserOutput, RendererOutput>; constructor(...args: MarkedExtension<ParserOutput, RendererOutput>[]) { this.use(...args); } /** * Run callback for every token */ walkTokens(tokens: Token[] | TokensList, callback: (token: Token) => MaybePromise | MaybePromise[]) { let values: MaybePromise[] = []; for (const token of tokens) { values = values.concat(callback.call(this, token)); switch (token.type) { case 'table': { const tableToken = token as Tokens.Table; for (const cell of tableToken.header) { values = values.concat(this.walkTokens(cell.tokens, callback)); } for (const row of tableToken.rows) { for (const cell of row) { values = values.concat(this.walkTokens(cell.tokens, callback)); } } break; } case 'list': { const listToken = token as Tokens.List; values = values.concat(this.walkTokens(listToken.items, callback)); break; } default: { const genericToken = token as Tokens.Generic; if (this.defaults.extensions?.childTokens?.[genericToken.type]) { this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => { const tokens = genericToken[childTokens].flat(Infinity) as Token[] | TokensList; values = values.concat(this.walkTokens(tokens, callback)); }); } else if (genericToken.tokens) { values = values.concat(this.walkTokens(genericToken.tokens, callback)); } } } } return values; } /** * Registers extensions with this Marked instance. * * The `renderer`, `tokenizer`, and `hooks` objects may each contain only the * methods that should be overridden. Their methods are merged with built-in * behavior and extensions registered by earlier calls. * * Use this method when supplying only some hook methods. */ use(...args: MarkedExtension<ParserOutput, RendererOutput>[]) { const extensions: MarkedOptions<ParserOutput, RendererOutput>['extensions'] = this.defaults.extensions || { renderers: {}, childTokens: {} }; args.forEach((pack) => { // copy options to new object const opts = { ...pack } as MarkedOptions<ParserOutput, RendererOutput>; // set async to true if it was set to true before opts.async = this.defaults.async || opts.async || false; // ==-- Parse "addon" extensions --== // if (pack.extensions) { pack.extensions.forEach((ext) => { if (!ext.name) { throw new Error('extension name required'); } if ('renderer' in ext) { // Renderer extensions const prevRenderer = extensions.renderers[ext.name]; if (prevRenderer) { // Replace extension with func to run new extension but fall back if false extensions.renderers[ext.name] = function(...args) { let ret = ext.renderer.apply(this, args); if (ret === false) { ret = prevRenderer.apply(this, args); } return ret; }; } else { extensions.renderers[ext.name] = ext.renderer; } } if ('tokenizer' in ext) { // Tokenizer Extensions if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) { throw new Error("extension level must be 'block' or 'inline'"); } const extLevel = extensions[ext.level]; if (extLevel) { extLevel.unshift(ext.tokenizer); } else { extensions[ext.level] = [ext.tokenizer]; } if (ext.start) { // Function to check for start of token if (ext.level === 'block') { if (extensions.startBlock) { extensions.startBlock.push(ext.start); } else { extensions.startBlock = [ext.start]; } } else if (ext.level === 'inline') { if (extensions.startInline) { extensions.startInline.push(ext.start); } else { extensions.startInline = [ext.start]; } } } } if ('childTokens' in ext && ext.childTokens) { // Child tokens to be visited by walkTokens extensions.childTokens[ext.name] = ext.childTokens; } }); opts.extensions = extensions; } // ==-- Parse "overwrite" extensions --== // if (pack.renderer) { const renderer = this.defaults.renderer || new _Renderer<ParserOutput, RendererOutput>(this.defaults); for (const prop in pack.renderer) { if (!(prop in renderer)) { throw new Error(`renderer '${prop}' does not exist`); } if (['options', 'parser'].includes(prop)) { // ignore options property continue; } const rendererProp = prop as Exclude<keyof _Renderer<ParserOutput, RendererOutput>, 'options' | 'parser'>; const rendererFunc = pack.renderer[rendererProp] as GenericRendererFunction; const prevRenderer = renderer[rendererProp] as GenericRendererFunction; // Replace renderer with func to run extension, but fall back if false renderer[rendererProp] = (...args: unknown[]) => { let ret = rendererFunc.apply(renderer, args); if (ret === false) { ret = prevRenderer.apply(renderer, args); } return (ret || '') as RendererOutput; }; } opts.renderer = renderer; } if (pack.tokenizer) { const tokenizer = this.defaults.tokenizer || new _Tokenizer<ParserOutput, RendererOutput>(this.defaults); for (const prop in pack.tokenizer) { if (!(prop in tokenizer)) { throw new Error(`tokenizer '${prop}' does not exist`); } if (['options', 'rules', 'lexer'].includes(prop)) { // ignore options, rules, and lexer properties continue; } const tokenizerProp = prop as Exclude<keyof _Tokenizer<ParserOutput, RendererOutput>, 'options' | 'rules' | 'lexer'>; const tokenizerFunc = pack.tokenizer[tokenizerProp] as UnknownFunction; const prevTokenizer = tokenizer[tokenizerProp] as UnknownFunction; // Replace tokenizer with func to run extension, but fall back if false // @ts-expect-error cannot type tokenizer function dynamically tokenizer[tokenizerProp] = (...args: unknown[]) => { let ret = tokenizerFunc.apply(tokenizer, args); if (ret === false) { ret = prevTokenizer.apply(tokenizer, args); } return ret; }; } opts.tokenizer = tokenizer; } // ==-- Parse Hooks extensions --== // if (pack.hooks) { const hooks = this.defaults.hooks || new _Hooks<ParserOutput, RendererOutput>(); for (const prop in pack.hooks) { if (!(prop in hooks)) { throw new Error(`hook '${prop}' does not exist`); } if (['options', 'block'].includes(prop)) { // ignore options and block properties continue; } const hooksProp = prop as Exclude<keyof _Hooks<ParserOutput, RendererOutput>, 'options' | 'block'>; const hooksFunc = pack.hooks[hooksProp] as UnknownFunction; const prevHook = hooks[hooksProp] as UnknownFunction; if (_Hooks.passThroughHooks.has(prop)) { // @ts-expect-error cannot type hook function dynamically hooks[hooksProp] = (arg: unknown) => { if (this.defaults.async && _Hooks.passThroughHooksRespectAsync.has(prop)) { return (async() => { const ret = await hooksFunc.call(hooks, arg); return prevHook.call(hooks, ret); })(); } const ret = hooksFunc.call(hooks, arg); return prevHook.call(hooks, ret); }; } else { // @ts-expect-error cannot type hook function dynamically hooks[hooksProp] = (...args: unknown[]) => { if (this.defaults.async) { return (async() => { let ret = await hooksFunc.apply(hooks, args); if (ret === false) { ret = await prevHook.apply(hooks, args); } return ret; })(); } let ret = hooksFunc.apply(hooks, args); if (ret === false) { ret = prevHook.apply(hooks, args); } return ret; }; } } opts.hooks = hooks; } // ==-- Parse WalkTokens extensions --== // if (pack.walkTokens) { const walkTokens = this.defaults.walkTokens; const packWalktokens = pack.walkTokens; opts.walkTokens = function(token) { let values: MaybePromise[] = []; values.push(packWalktokens.call(this, token)); if (walkTokens) { values = values.concat(walkTokens.call(this, token)); } return values; }; } this.defaults = { ...this.defaults, ...opts }; }); return this; } setOptions(opt: MarkedOptions<ParserOutput, RendererOutput>) { this.defaults = { ...this.defaults, ...opt }; return this; } lexer(src: string, options?: MarkedOptions<ParserOutput, RendererOutput>) { return _Lexer.lex(src, options ?? this.defaults); } parser(tokens: Token[], options?: MarkedOptions<ParserOutput, RendererOutput>) { return _Parser.parse<ParserOutput, RendererOutput>(tokens, options ?? this.defaults); } private parseMarkdown(blockType: boolean) { type overloadedParse = { (src: string, options: MarkedOptions<ParserOutput, RendererOutput> & { async: true }): Promise<ParserOutput>; (src: string, options: MarkedOptions<ParserOutput, RendererOutput> & { async: false }): ParserOutput; (src: string, options?: MarkedOptions<ParserOutput, RendererOutput> | null): ParserOutput | Promise<ParserOutput>; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const parse: overloadedParse = (src: string, options?: MarkedOptions<ParserOutput, RendererOutput> | null): any => { const origOpt = { ...options }; const opt = { ...this.defaults, ...origOpt }; 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) { 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.')); } // 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')); } if (typeof src !== 'string') { return throwError(new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected')); } if (opt.hooks) { opt.hooks.options = opt; opt.hooks.block = blockType; } if (opt.async) { return (async() => { const processedSrc = opt.hooks ? await opt.hooks.preprocess(src) : src; const lexer = opt.hooks ? await opt.hooks.provideLexer(blockType) : (blockType ? _Lexer.lex : _Lexer.lexInline); const tokens = await lexer(processedSrc, opt); const processedTokens = opt.hooks ? await opt.hooks.processAllTokens(tokens) : tokens; if (opt.walkTokens) { await Promise.all(this.walkTokens(processedTokens, opt.walkTokens)); } const parser = opt.hooks ? await opt.hooks.provideParser(blockType) : (blockType ? _Parser.parse : _Parser.parseInline); const html = await parser(processedTokens, opt); return opt.hooks ? await opt.hooks.postprocess(html) : html; })().catch(throwError); } try { if (opt.hooks) { src = opt.hooks.preprocess(src) as string; } const lexer = opt.hooks ? opt.hooks.provideLexer(blockType) : (blockType ? _Lexer.lex : _Lexer.lexInline); let tokens = lexer(src, opt); if (opt.hooks) { tokens = opt.hooks.processAllTokens(tokens); } if (opt.walkTokens) { this.walkTokens(tokens, opt.walkTokens); } const parser = opt.hooks ? opt.hooks.provideParser(blockType) : (blockType ? _Parser.parse : _Parser.parseInline); let html = parser(tokens, opt); if (opt.hooks) { html = opt.hooks.postprocess(html); } return html; } catch(e) { return throwError(e as Error); } }; return parse; } private onError(silent: boolean, async: boolean) { return (e: Error): string | Promise<string> => { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (silent) { const msg = '<p>An error occurred:</p><pre>' + escapeHtmlEntities(e.message + '', true) + '</pre>'; if (async) { return Promise.resolve(msg); } return msg; } if (async) { return Promise.reject(e); } throw e; }; } }