• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

markdown-it / markdown-it / 30162549528

25 Jul 2026 02:53PM UTC coverage: 99.808% (-0.005%) from 99.813%
30162549528

push

github

puzrin
doc: organize navigation

1720 of 1739 branches covered (98.91%)

15 of 15 new or added lines in 7 files covered. (100.0%)

3 existing lines in 1 file now uncovered.

6239 of 6251 relevant lines covered (99.81%)

215698.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.13
/src/renderer.ts
1
import { unescapeAll, escapeHtml } from './common/utils.ts'
33✔
2
import type Token from './token.ts'
33✔
3
import type { Env, MarkdownItOptions } from './types.ts'
33✔
4

33✔
5
type RendererRule = (
33✔
6
  tokens: Token[],
33✔
7
  idx: number,
33✔
8
  options: MarkdownItOptions,
33✔
9
  env: Env | undefined,
33✔
10
  renderer: Renderer
33✔
11
) => string
33✔
12

33✔
13
const default_rules: Record<string, RendererRule> = {}
33✔
14

33✔
15
default_rules.code_inline = function (
33✔
16
  tokens: Token[],
46✔
17
  idx: number,
46✔
18
  options: MarkdownItOptions,
46✔
19
  env: Env | undefined,
46✔
20
  slf: Renderer
46✔
21
): string {
46✔
22
  const token = tokens[idx]
46✔
23

46✔
24
  return `<code${slf.renderAttrs(token)}>${escapeHtml(token.content)}</code>`
46✔
25
}
46✔
26

33✔
27
default_rules.code_block = function (
33✔
28
  tokens: Token[],
71✔
29
  idx: number,
71✔
30
  options: MarkdownItOptions,
71✔
31
  env: Env | undefined,
71✔
32
  slf: Renderer
71✔
33
): string {
71✔
34
  const token = tokens[idx]
71✔
35

71✔
36
  return `<pre${slf.renderAttrs(token)}><code>${escapeHtml(tokens[idx].content)}</code></pre>\n`
71✔
37
}
71✔
38

33✔
39
default_rules.fence = function (
33✔
40
  tokens: Token[],
47✔
41
  idx: number,
47✔
42
  options: MarkdownItOptions,
47✔
43
  env: Env | undefined,
47✔
44
  slf: Renderer
47✔
45
): string {
47✔
46
  const token = tokens[idx]
47✔
47
  const info = token.info ? unescapeAll(token.info).trim() : ''
47✔
48
  let langName = ''
47✔
49
  let langAttrs = ''
47✔
50

47✔
51
  if (info) {
47✔
52
    const arr = info.split(/(\s+)/g)
10✔
53
    langName = arr[0]
10✔
54
    langAttrs = arr.slice(2).join('')
10✔
55
  }
10✔
56

47✔
57
  let highlighted
47✔
58
  if (options.highlight) {
47✔
59
    highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content)
3✔
60
  } else {
47✔
61
    highlighted = escapeHtml(token.content)
44✔
62
  }
44✔
63

47✔
64
  if (highlighted.indexOf('<pre') === 0) {
47✔
65
    return highlighted + '\n'
2✔
66
  }
2✔
67

45✔
68
  // If language exists, inject class gently, without modifying original token.
45✔
69
  // May be, one day we will add .deepClone() for token and simplify this part, but
45✔
70
  // now we prefer to keep things local.
45✔
71
  if (info) {
47✔
72
    const i = token.attrIndex('class')
9✔
73
    const tmpAttrs = token.attrs ? token.attrs.slice() : []
9!
74

9✔
75
    if (i < 0) {
9✔
76
      tmpAttrs.push(['class', `${options.langPrefix}${langName}`])
9✔
77
    } else {
9!
UNCOV
78
      tmpAttrs[i] = [tmpAttrs[i][0], tmpAttrs[i][1]] // shallow clone
×
UNCOV
79
      tmpAttrs[i][1] += ` ${options.langPrefix}${langName}`
×
UNCOV
80
    }
×
81

9✔
82
    // Fake token just to render attributes
9✔
83
    const tmpToken = {
9✔
84
      attrs: tmpAttrs
9✔
85
    }
9✔
86

9✔
87
    return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code></pre>\n`
9✔
88
  }
9✔
89

36✔
90
  return `<pre><code${slf.renderAttrs(token)}>${highlighted}</code></pre>\n`
36✔
91
}
47✔
92

33✔
93
default_rules.image = function (
33✔
94
  tokens: Token[],
32✔
95
  idx: number,
32✔
96
  options: MarkdownItOptions,
32✔
97
  env: Env | undefined,
32✔
98
  slf: Renderer
32✔
99
): string {
32✔
100
  const token = tokens[idx]
32✔
101

32✔
102
  // "alt" attr MUST be set, even if empty. Because it's mandatory and
32✔
103
  // should be placed on proper position for tests.
32✔
104
  //
32✔
105
  // Replace content with actual value
32✔
106

32✔
107
  token.attrs![token.attrIndex('alt')][1] =
32✔
108
    slf.renderInlineAsText(token.children!, options, env)
32✔
109

32✔
110
  return slf.renderToken(tokens, idx, options)
32✔
111
}
32✔
112

33✔
113
default_rules.hardbreak = function (
33✔
114
  tokens: Token[],
16✔
115
  idx: number,
16✔
116
  options: MarkdownItOptions
16✔
117
): string {
16✔
118
  return options.xhtmlOut ? '<br />\n' : '<br>\n'
16✔
119
}
16✔
120
default_rules.softbreak = function (
33✔
121
  tokens: Token[],
145✔
122
  idx: number,
145✔
123
  options: MarkdownItOptions
145✔
124
): string {
145✔
125
  return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n'
145✔
126
}
145✔
127

33✔
128
default_rules.text = function (tokens: Token[], idx: number): string {
33✔
129
  return escapeHtml(tokens[idx].content)
241,640✔
130
}
241,640✔
131

33✔
132
default_rules.html_block = function (tokens: Token[], idx: number): string {
33✔
133
  return tokens[idx].content
63✔
134
}
63✔
135
default_rules.html_inline = function (tokens: Token[], idx: number): string {
33✔
136
  return tokens[idx].content
46✔
137
}
46✔
138

33✔
139
/**
33✔
140
 * Generates HTML from parsed token stream. Each instance has independent
33✔
141
 * copy of rules. Those can be rewritten with ease. Also, you can add new
33✔
142
 * rules if you create plugin and adds new token types.
33✔
143
 *
33✔
144
 * Creates new renderer instance and fills {@link Renderer.rules} with defaults.
33✔
145
 */
33✔
146
class Renderer {
33✔
147
  /**
33✔
148
   * Contains render rules for tokens. Can be updated and extended.
33✔
149
   *
33✔
150
   * See [source code](https://github.com/markdown-it/markdown-it/blob/master/src/renderer.ts)
33✔
151
   * for more details and examples.
33✔
152
   *
33✔
153
   * @example Custom render rules
33✔
154
   * ```javascript
33✔
155
   * var md = require('markdown-it')();
33✔
156
   *
33✔
157
   * md.renderer.rules.strong_open  = function () { return '<b>'; };
33✔
158
   * md.renderer.rules.strong_close = function () { return '</b>'; };
33✔
159
   *
33✔
160
   * var result = md.renderInline(...);
33✔
161
   * ```
33✔
162
   *
33✔
163
   * @example Each rule is called as independent static function with fixed signature
33✔
164
   * ```javascript
33✔
165
   * function my_token_render(tokens, idx, options, env, renderer) {
33✔
166
   *   // ...
33✔
167
   *   return renderedHTML;
33✔
168
   * }
33✔
169
   * ```
33✔
170
   */
33✔
171
  rules: Record<string, RendererRule> = Object.assign({}, default_rules)
33✔
172

33✔
173
  /**
33✔
174
   * Render token attributes to string.
33✔
175
   */
33✔
176
  renderAttrs (token: Pick<Token, 'attrs'>): string {
33✔
177
    let i, l, result
265,271✔
178

265,271✔
179
    if (!token.attrs) { return '' }
265,271✔
180

20,279✔
181
    result = ''
20,279✔
182

20,279✔
183
    for (i = 0, l = token.attrs.length; i < l; i++) {
24,217✔
184
      result += ` ${escapeHtml(token.attrs[i][0])}="${escapeHtml(String(token.attrs[i][1]))}"`
20,364✔
185
    }
20,364✔
186

20,279✔
187
    return result
20,279✔
188
  }
265,271✔
189

33✔
190
  /**
33✔
191
   * Default token renderer. Can be overriden by custom function
33✔
192
   * in {@link Renderer.rules}.
33✔
193
   *
33✔
194
   * @param tokens List of tokens.
33✔
195
   * @param idx Token index to render.
33✔
196
   * @param options Params of parser instance.
33✔
197
   */
33✔
198
  renderToken (tokens: Token[], idx: number, options: MarkdownItOptions): string {
33✔
199
    const token = tokens[idx]
265,423✔
200
    let result = ''
265,423✔
201

265,423✔
202
    // Tight list paragraphs
265,423✔
203
    if (token.hidden) {
265,423✔
204
      return ''
314✔
205
    }
314✔
206

265,109✔
207
    // Insert a newline between hidden paragraph and subsequent opening
265,109✔
208
    // block-level tag.
265,109✔
209
    //
265,109✔
210
    // For example, here we should insert a newline before blockquote:
265,109✔
211
    //  - a
265,109✔
212
    //    >
265,109✔
213
    //
265,109✔
214
    if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) {
265,423✔
215
      result += '\n'
64✔
216
    }
64✔
217

265,109✔
218
    // Add token name, e.g. `<img`
265,109✔
219
    result += (token.nesting === -1 ? '</' : '<') + token.tag
265,423✔
220

265,423✔
221
    // Encode attributes, e.g. `<img src="foo"`
265,423✔
222
    result += this.renderAttrs(token)
265,423✔
223

265,423✔
224
    // Add a slash for self-closing tags, e.g. `<img src="foo" /`
265,423✔
225
    if (token.nesting === 0 && options.xhtmlOut) {
265,423✔
226
      result += ' /'
57✔
227
    }
57✔
228

265,109✔
229
    // Check if we need to add a newline after this tag
265,109✔
230
    let needLf = false
265,109✔
231
    if (token.block) {
265,325✔
232
      needLf = true
4,265✔
233

4,265✔
234
      if (token.nesting === 1) {
4,265✔
235
        if (idx + 1 < tokens.length) {
2,111✔
236
          const nextToken = tokens[idx + 1]
2,111✔
237

2,111✔
238
          if (nextToken.type === 'inline' || nextToken.hidden) {
2,111✔
239
          // Block-level tag containing an inline tag.
1,234✔
240
          //
1,234✔
241
            needLf = false
1,234✔
242
          } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) {
2,111✔
243
          // Opening tag + closing tag of the same type. E.g. `<li></li>`.
16✔
244
          //
16✔
245
            needLf = false
16✔
246
          }
16✔
247
        }
2,111✔
248
      }
2,111✔
249
    }
4,265✔
250

265,109✔
251
    result += needLf ? '>\n' : '>'
265,423✔
252

265,423✔
253
    return result
265,423✔
254
  }
265,423✔
255

33✔
256
  /**
33✔
257
   * The same as {@link Renderer.render}, but for single token of `inline` type.
33✔
258
   *
33✔
259
   * @param tokens List on block tokens to render.
33✔
260
   * @param options Params of parser instance.
33✔
261
   * @param env Additional data from parsed input (references, for example).
33✔
262
   */
33✔
263
  renderInline (tokens: Token[], options: MarkdownItOptions, env: Env | undefined): string {
33✔
264
    let result = ''
1,241✔
265
    const rules = this.rules
1,241✔
266

1,241✔
267
    for (let i = 0, len = tokens.length; i < len; i++) {
1,241✔
268
      const type = tokens[i].type
502,739✔
269

502,739✔
270
      if (typeof rules[type] !== 'undefined') {
502,739✔
271
        result += rules[type](tokens, i, options, env, this)
241,927✔
272
      } else {
362,737✔
273
        result += this.renderToken(tokens, i, options)
260,812✔
274
      }
260,812✔
275
    }
502,739✔
276

1,241✔
277
    return result
1,241✔
278
  }
1,241✔
279

33✔
280
  /**
33✔
281
   * Special kludge for image `alt` attributes to conform CommonMark spec.
33✔
282
   * Don't try to use it! Spec requires to show `alt` content with stripped markup,
33✔
283
   * instead of simple escaping.
33✔
284
   *
33✔
285
   * @param tokens List on block tokens to render.
33✔
286
   * @param options Params of parser instance.
33✔
287
   * @param env Additional data from parsed input (references, for example).
33✔
288
   */
33✔
289
  renderInlineAsText (tokens: Token[], options: MarkdownItOptions, env: Env | undefined): string {
33✔
290
    let result = ''
33✔
291

33✔
292
    for (let i = 0, len = tokens.length; i < len; i++) {
33✔
293
      switch (tokens[i].type) {
68✔
294
        case 'text':
68✔
295
          result += tokens[i].content
44✔
296
          break
44✔
297
        case 'image':
68✔
298
          result += this.renderInlineAsText(tokens[i].children!, options, env)
1✔
299
          break
1✔
300
        case 'html_inline':
68✔
301
        case 'html_block':
68✔
302
          result += tokens[i].content
1✔
303
          break
1✔
304
        case 'softbreak':
68✔
305
        case 'hardbreak':
68✔
306
          result += '\n'
2✔
307
          break
2✔
308
        default:
68✔
309
        // all other tokens are skipped
68✔
310
      }
68✔
311
    }
68✔
312

33✔
313
    return result
33✔
314
  }
33✔
315

33✔
316
  /**
33✔
317
   * Takes token stream and generates HTML. Probably, you will never need to call
33✔
318
   * this method directly.
33✔
319
   *
33✔
320
   * @param tokens List on block tokens to render.
33✔
321
   * @param options Params of parser instance.
33✔
322
   * @param env Additional data from parsed input (references, for example).
33✔
323
   */
33✔
324
  render (tokens: Token[], options: MarkdownItOptions, env?: Env): string {
33✔
325
    let result = ''
944✔
326
    const rules = this.rules
944✔
327

944✔
328
    for (let i = 0, len = tokens.length; i < len; i++) {
944✔
329
      const type = tokens[i].type
6,003✔
330

6,003✔
331
      if (type === 'inline') {
6,003✔
332
        result += this.renderInline(tokens[i].children!, options, env)
1,241✔
333
      } else if (typeof rules[type] !== 'undefined') {
6,003✔
334
        result += rules[type](tokens, i, options, env, this)
183✔
335
      } else {
4,762✔
336
        result += this.renderToken(tokens, i, options)
4,579✔
337
      }
4,579✔
338
    }
6,003✔
339

944✔
340
    return result
944✔
341
  }
944✔
342
}
33✔
343

33✔
344
export default Renderer
33✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc