Skip to content

Commit 39a0ee3

Browse files
authored
feat: add generic types for parser and renderer output (#3722)
* fix: add generic types for parser and renderer output * fix types * rename generic variables * add tests
1 parent 1d687e0 commit 39a0ee3

File tree

11 files changed

+183
-169
lines changed

11 files changed

+183
-169
lines changed

src/Hooks.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { _Parser } from './Parser.ts';
44
import type { MarkedOptions } from './MarkedOptions.ts';
55
import type { Token, TokensList } from './Tokens.ts';
66

7-
export class _Hooks {
8-
options: MarkedOptions;
7+
export class _Hooks<ParserOutput = string, RendererOutput = string> {
8+
options: MarkedOptions<ParserOutput, RendererOutput>;
99
block?: boolean;
1010

11-
constructor(options?: MarkedOptions) {
11+
constructor(options?: MarkedOptions<ParserOutput, RendererOutput>) {
1212
this.options = options || _defaults;
1313
}
1414

@@ -28,7 +28,7 @@ export class _Hooks {
2828
/**
2929
* Process HTML after marked is finished
3030
*/
31-
postprocess(html: string) {
31+
postprocess(html: ParserOutput) {
3232
return html;
3333
}
3434

@@ -50,6 +50,6 @@ export class _Hooks {
5050
* Provide function to parse tokens
5151
*/
5252
provideParser() {
53-
return this.block ? _Parser.parse : _Parser.parseInline;
53+
return this.block ? _Parser.parse<ParserOutput, RendererOutput> : _Parser.parseInline<ParserOutput, RendererOutput>;
5454
}
5555
}

src/Instance.ts

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ export type MaybePromise = void | Promise<void>;
1414
type UnknownFunction = (...args: unknown[]) => unknown;
1515
type GenericRendererFunction = (...args: unknown[]) => string | false;
1616

17-
export class Marked {
18-
defaults = _getDefaults();
17+
export class Marked<ParserOutput = string, RendererOutput = string> {
18+
defaults = _getDefaults<ParserOutput, RendererOutput>();
1919
options = this.setOptions;
2020

2121
parse = this.parseMarkdown(true);
2222
parseInline = this.parseMarkdown(false);
2323

24-
Parser = _Parser;
25-
Renderer = _Renderer;
26-
TextRenderer = _TextRenderer;
24+
Parser = _Parser<ParserOutput, RendererOutput>;
25+
Renderer = _Renderer<ParserOutput, RendererOutput>;
26+
TextRenderer = _TextRenderer<RendererOutput>;
2727
Lexer = _Lexer;
28-
Tokenizer = _Tokenizer;
29-
Hooks = _Hooks;
28+
Tokenizer = _Tokenizer<ParserOutput, RendererOutput>;
29+
Hooks = _Hooks<ParserOutput, RendererOutput>;
3030

31-
constructor(...args: MarkedExtension[]) {
31+
constructor(...args: MarkedExtension<ParserOutput, RendererOutput>[]) {
3232
this.use(...args);
3333
}
3434

@@ -73,12 +73,12 @@ export class Marked {
7373
return values;
7474
}
7575

76-
use(...args: MarkedExtension[]) {
77-
const extensions: MarkedOptions['extensions'] = this.defaults.extensions || { renderers: {}, childTokens: {} };
76+
use(...args: MarkedExtension<ParserOutput, RendererOutput>[]) {
77+
const extensions: MarkedOptions<ParserOutput, RendererOutput>['extensions'] = this.defaults.extensions || { renderers: {}, childTokens: {} };
7878

7979
args.forEach((pack) => {
8080
// copy options to new object
81-
const opts = { ...pack } as MarkedOptions;
81+
const opts = { ...pack } as MarkedOptions<ParserOutput, RendererOutput>;
8282

8383
// set async to true if it was set to true before
8484
opts.async = this.defaults.async || opts.async || false;
@@ -139,7 +139,7 @@ export class Marked {
139139

140140
// ==-- Parse "overwrite" extensions --== //
141141
if (pack.renderer) {
142-
const renderer = this.defaults.renderer || new _Renderer(this.defaults);
142+
const renderer = this.defaults.renderer || new _Renderer<ParserOutput, RendererOutput>(this.defaults);
143143
for (const prop in pack.renderer) {
144144
if (!(prop in renderer)) {
145145
throw new Error(`renderer '${prop}' does not exist`);
@@ -148,7 +148,7 @@ export class Marked {
148148
// ignore options property
149149
continue;
150150
}
151-
const rendererProp = prop as Exclude<keyof _Renderer, 'options' | 'parser'>;
151+
const rendererProp = prop as Exclude<keyof _Renderer<ParserOutput, RendererOutput>, 'options' | 'parser'>;
152152
const rendererFunc = pack.renderer[rendererProp] as GenericRendererFunction;
153153
const prevRenderer = renderer[rendererProp] as GenericRendererFunction;
154154
// Replace renderer with func to run extension, but fall back if false
@@ -157,13 +157,13 @@ export class Marked {
157157
if (ret === false) {
158158
ret = prevRenderer.apply(renderer, args);
159159
}
160-
return ret || '';
160+
return (ret || '') as RendererOutput;
161161
};
162162
}
163163
opts.renderer = renderer;
164164
}
165165
if (pack.tokenizer) {
166-
const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
166+
const tokenizer = this.defaults.tokenizer || new _Tokenizer<ParserOutput, RendererOutput>(this.defaults);
167167
for (const prop in pack.tokenizer) {
168168
if (!(prop in tokenizer)) {
169169
throw new Error(`tokenizer '${prop}' does not exist`);
@@ -172,7 +172,7 @@ export class Marked {
172172
// ignore options, rules, and lexer properties
173173
continue;
174174
}
175-
const tokenizerProp = prop as Exclude<keyof _Tokenizer, 'options' | 'rules' | 'lexer'>;
175+
const tokenizerProp = prop as Exclude<keyof _Tokenizer<ParserOutput, RendererOutput>, 'options' | 'rules' | 'lexer'>;
176176
const tokenizerFunc = pack.tokenizer[tokenizerProp] as UnknownFunction;
177177
const prevTokenizer = tokenizer[tokenizerProp] as UnknownFunction;
178178
// Replace tokenizer with func to run extension, but fall back if false
@@ -190,7 +190,7 @@ export class Marked {
190190

191191
// ==-- Parse Hooks extensions --== //
192192
if (pack.hooks) {
193-
const hooks = this.defaults.hooks || new _Hooks();
193+
const hooks = this.defaults.hooks || new _Hooks<ParserOutput, RendererOutput>();
194194
for (const prop in pack.hooks) {
195195
if (!(prop in hooks)) {
196196
throw new Error(`hook '${prop}' does not exist`);
@@ -199,7 +199,7 @@ export class Marked {
199199
// ignore options and block properties
200200
continue;
201201
}
202-
const hooksProp = prop as Exclude<keyof _Hooks, 'options' | 'block'>;
202+
const hooksProp = prop as Exclude<keyof _Hooks<ParserOutput, RendererOutput>, 'options' | 'block'>;
203203
const hooksFunc = pack.hooks[hooksProp] as UnknownFunction;
204204
const prevHook = hooks[hooksProp] as UnknownFunction;
205205
if (_Hooks.passThroughHooks.has(prop)) {
@@ -248,28 +248,28 @@ export class Marked {
248248
return this;
249249
}
250250

251-
setOptions(opt: MarkedOptions) {
251+
setOptions(opt: MarkedOptions<ParserOutput, RendererOutput>) {
252252
this.defaults = { ...this.defaults, ...opt };
253253
return this;
254254
}
255255

256-
lexer(src: string, options?: MarkedOptions) {
256+
lexer(src: string, options?: MarkedOptions<ParserOutput, RendererOutput>) {
257257
return _Lexer.lex(src, options ?? this.defaults);
258258
}
259259

260-
parser(tokens: Token[], options?: MarkedOptions) {
261-
return _Parser.parse(tokens, options ?? this.defaults);
260+
parser(tokens: Token[], options?: MarkedOptions<ParserOutput, RendererOutput>) {
261+
return _Parser.parse<ParserOutput, RendererOutput>(tokens, options ?? this.defaults);
262262
}
263263

264264
private parseMarkdown(blockType: boolean) {
265265
type overloadedParse = {
266-
(src: string, options: MarkedOptions & { async: true }): Promise<string>;
267-
(src: string, options: MarkedOptions & { async: false }): string;
268-
(src: string, options?: MarkedOptions | null): string | Promise<string>;
266+
(src: string, options: MarkedOptions<ParserOutput, RendererOutput> & { async: true }): Promise<ParserOutput>;
267+
(src: string, options: MarkedOptions<ParserOutput, RendererOutput> & { async: false }): ParserOutput;
268+
(src: string, options?: MarkedOptions<ParserOutput, RendererOutput> | null): ParserOutput | Promise<ParserOutput>;
269269
};
270270

271271
// eslint-disable-next-line @typescript-eslint/no-explicit-any
272-
const parse: overloadedParse = (src: string, options?: MarkedOptions | null): any => {
272+
const parse: overloadedParse = (src: string, options?: MarkedOptions<ParserOutput, RendererOutput> | null): any => {
273273
const origOpt = { ...options };
274274
const opt = { ...this.defaults, ...origOpt };
275275

@@ -320,7 +320,7 @@ export class Marked {
320320
}
321321
let html = parser(tokens, opt);
322322
if (opt.hooks) {
323-
html = opt.hooks.postprocess(html) as string;
323+
html = opt.hooks.postprocess(html);
324324
}
325325
return html;
326326
} catch(e) {

src/Lexer.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@ import type { MarkedOptions } from './MarkedOptions.ts';
77
/**
88
* Block Lexer
99
*/
10-
export class _Lexer {
10+
export class _Lexer<ParserOutput = string, RendererOutput = string> {
1111
tokens: TokensList;
12-
options: MarkedOptions;
12+
options: MarkedOptions<ParserOutput, RendererOutput>;
1313
state: {
1414
inLink: boolean;
1515
inRawBlock: boolean;
1616
top: boolean;
1717
};
1818

19-
private tokenizer: _Tokenizer;
19+
private tokenizer: _Tokenizer<ParserOutput, RendererOutput>;
2020
private inlineQueue: { src: string, tokens: Token[] }[];
2121

22-
constructor(options?: MarkedOptions) {
22+
constructor(options?: MarkedOptions<ParserOutput, RendererOutput>) {
2323
// TokenList cannot be created in one go
2424
this.tokens = [] as unknown as TokensList;
2525
this.tokens.links = Object.create(null);
2626
this.options = options || _defaults;
27-
this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
27+
this.options.tokenizer = this.options.tokenizer || new _Tokenizer<ParserOutput, RendererOutput>();
2828
this.tokenizer = this.options.tokenizer;
2929
this.tokenizer.options = this.options;
3030
this.tokenizer.lexer = this;
@@ -68,16 +68,16 @@ export class _Lexer {
6868
/**
6969
* Static Lex Method
7070
*/
71-
static lex(src: string, options?: MarkedOptions) {
72-
const lexer = new _Lexer(options);
71+
static lex<ParserOutput = string, RendererOutput = string>(src: string, options?: MarkedOptions<ParserOutput, RendererOutput>) {
72+
const lexer = new _Lexer<ParserOutput, RendererOutput>(options);
7373
return lexer.lex(src);
7474
}
7575

7676
/**
7777
* Static Lex Inline Method
7878
*/
79-
static lexInline(src: string, options?: MarkedOptions) {
80-
const lexer = new _Lexer(options);
79+
static lexInline<ParserOutput = string, RendererOutput = string>(src: string, options?: MarkedOptions<ParserOutput, RendererOutput>) {
80+
const lexer = new _Lexer<ParserOutput, RendererOutput>(options);
8181
return lexer.inlineTokens(src);
8282
}
8383

src/MarkedOptions.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,35 @@ export interface TokenizerExtension {
2121
childTokens?: string[];
2222
}
2323

24-
export interface RendererThis {
25-
parser: _Parser;
24+
export interface RendererThis<ParserOutput = string, RendererOutput = string> {
25+
parser: _Parser<ParserOutput, RendererOutput>;
2626
}
2727

28-
export type RendererExtensionFunction = (this: RendererThis, token: Tokens.Generic) => string | false | undefined;
28+
export type RendererExtensionFunction<ParserOutput = string, RendererOutput = string> = (this: RendererThis<ParserOutput, RendererOutput>, token: Tokens.Generic) => RendererOutput | false | undefined;
2929

30-
export interface RendererExtension {
30+
export interface RendererExtension<ParserOutput = string, RendererOutput = string> {
3131
name: string;
32-
renderer: RendererExtensionFunction;
32+
renderer: RendererExtensionFunction<ParserOutput, RendererOutput>;
3333
}
3434

35-
export type TokenizerAndRendererExtension = TokenizerExtension | RendererExtension | (TokenizerExtension & RendererExtension);
35+
export type TokenizerAndRendererExtension<ParserOutput = string, RendererOutput = string> = TokenizerExtension | RendererExtension<ParserOutput, RendererOutput> | (TokenizerExtension & RendererExtension<ParserOutput, RendererOutput>);
3636

37-
type HooksApi = Omit<_Hooks, 'constructor' | 'options' | 'block'>;
38-
type HooksObject = {
39-
[K in keyof HooksApi]?: (this: _Hooks, ...args: Parameters<HooksApi[K]>) => ReturnType<HooksApi[K]> | Promise<ReturnType<HooksApi[K]>>
37+
type HooksApi<ParserOutput = string, RendererOutput = string> = Omit<_Hooks<ParserOutput, RendererOutput>, 'constructor' | 'options' | 'block'>;
38+
type HooksObject<ParserOutput = string, RendererOutput = string> = {
39+
[K in keyof HooksApi<ParserOutput, RendererOutput>]?: (this: _Hooks<ParserOutput, RendererOutput>, ...args: Parameters<HooksApi<ParserOutput, RendererOutput>[K]>) => ReturnType<HooksApi<ParserOutput, RendererOutput>[K]> | Promise<ReturnType<HooksApi<ParserOutput, RendererOutput>[K]>>
4040
};
4141

42-
type RendererApi = Omit<_Renderer, 'constructor' | 'options' | 'parser'>;
43-
type RendererObject = {
44-
[K in keyof RendererApi]?: (this: _Renderer, ...args: Parameters<RendererApi[K]>) => ReturnType<RendererApi[K]> | false
42+
type RendererApi<ParserOutput = string, RendererOutput = string> = Omit<_Renderer<ParserOutput, RendererOutput>, 'constructor' | 'options' | 'parser'>;
43+
type RendererObject<ParserOutput = string, RendererOutput = string> = {
44+
[K in keyof RendererApi<ParserOutput, RendererOutput>]?: (this: _Renderer<ParserOutput, RendererOutput>, ...args: Parameters<RendererApi<ParserOutput, RendererOutput>[K]>) => ReturnType<RendererApi<ParserOutput, RendererOutput>[K]> | false
4545
};
4646

47-
type TokenizerApi = Omit<_Tokenizer, 'constructor' | 'options' | 'rules' | 'lexer'>;
48-
type TokenizerObject = {
49-
[K in keyof TokenizerApi]?: (this: _Tokenizer, ...args: Parameters<TokenizerApi[K]>) => ReturnType<TokenizerApi[K]> | false
47+
type TokenizerApi<ParserOutput = string, RendererOutput = string> = Omit<_Tokenizer<ParserOutput, RendererOutput>, 'constructor' | 'options' | 'rules' | 'lexer'>;
48+
type TokenizerObject<ParserOutput = string, RendererOutput = string> = {
49+
[K in keyof TokenizerApi<ParserOutput, RendererOutput>]?: (this: _Tokenizer<ParserOutput, RendererOutput>, ...args: Parameters<TokenizerApi<ParserOutput, RendererOutput>[K]>) => ReturnType<TokenizerApi<ParserOutput, RendererOutput>[K]> | false
5050
};
5151

52-
export interface MarkedExtension {
52+
export interface MarkedExtension<ParserOutput = string, RendererOutput = string> {
5353
/**
5454
* True will tell marked to await any walkTokens functions before parsing the tokens and returning an HTML string.
5555
*/
@@ -64,7 +64,7 @@ export interface MarkedExtension {
6464
* Add tokenizers and renderers to marked
6565
*/
6666
extensions?:
67-
| TokenizerAndRendererExtension[]
67+
| TokenizerAndRendererExtension<ParserOutput, RendererOutput>[]
6868
| null;
6969

7070
/**
@@ -80,7 +80,7 @@ export interface MarkedExtension {
8080
* provideLexer is called to provide a function to tokenize markdown.
8181
* provideParser is called to provide a function to parse tokens.
8282
*/
83-
hooks?: HooksObject | null;
83+
hooks?: HooksObject<ParserOutput, RendererOutput> | null;
8484

8585
/**
8686
* Conform to obscure parts of markdown.pl as much as possible. Don't fix any of the original markdown bugs or poor behavior.
@@ -92,7 +92,7 @@ export interface MarkedExtension {
9292
*
9393
* An object containing functions to render tokens to HTML.
9494
*/
95-
renderer?: RendererObject | null;
95+
renderer?: RendererObject<ParserOutput, RendererOutput> | null;
9696

9797
/**
9898
* Shows an HTML error message when rendering fails.
@@ -113,30 +113,30 @@ export interface MarkedExtension {
113113
walkTokens?: ((token: Token) => void | Promise<void>) | null;
114114
}
115115

116-
export interface MarkedOptions extends Omit<MarkedExtension, 'hooks' | 'renderer' | 'tokenizer' | 'extensions' | 'walkTokens'> {
116+
export interface MarkedOptions<ParserOutput = string, RendererOutput = string> extends Omit<MarkedExtension<ParserOutput, RendererOutput>, 'hooks' | 'renderer' | 'tokenizer' | 'extensions' | 'walkTokens'> {
117117
/**
118118
* Hooks are methods that hook into some part of marked.
119119
*/
120-
hooks?: _Hooks | null;
120+
hooks?: _Hooks<ParserOutput, RendererOutput> | null;
121121

122122
/**
123123
* Type: object Default: new Renderer()
124124
*
125125
* An object containing functions to render tokens to HTML.
126126
*/
127-
renderer?: _Renderer | null;
127+
renderer?: _Renderer<ParserOutput, RendererOutput> | null;
128128

129129
/**
130130
* The tokenizer defines how to turn markdown text into tokens.
131131
*/
132-
tokenizer?: _Tokenizer | null;
132+
tokenizer?: _Tokenizer<ParserOutput, RendererOutput> | null;
133133

134134
/**
135135
* Custom extensions
136136
*/
137137
extensions?: null | {
138138
renderers: {
139-
[name: string]: RendererExtensionFunction;
139+
[name: string]: RendererExtensionFunction<ParserOutput, RendererOutput>;
140140
};
141141
childTokens: {
142142
[name: string]: string[];

0 commit comments

Comments
 (0)