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

markdown-it / markdown-it / 29958590268

22 Jul 2026 09:16PM UTC coverage: 99.813%. Remained the same
29958590268

push

github

puzrin
Bump deps (to use built-in types)

1720 of 1739 branches covered (98.91%)

6408 of 6420 relevant lines covered (99.81%)

214880.61 hits per line

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

97.73
/src/rules_inline/state_inline.ts
1
// Inline parser state
33✔
2

33✔
3
import Token from '../token.ts'
33✔
4
import { isWhiteSpace, isPunctCharCode, isMdAsciiPunct } from '../common/utils.ts'
33✔
5
import type MarkdownIt from '../markdownit.ts'
33✔
6
import type { Delimiter, Env } from '../types.ts'
33✔
7

33✔
8
interface ScannedDelimiters {
33✔
9
  can_open: boolean
33✔
10
  can_close: boolean
33✔
11
  length: number
33✔
12
}
33✔
13

33✔
14
type StateTokenMeta = Record<string, unknown> & {
33✔
15
  delimiters?: Delimiter[]
33✔
16
}
33✔
17

33✔
18
class StateInline {
33✔
19
  declare src: string
33✔
20
  declare env: Env
33✔
21
  declare md: MarkdownIt
33✔
22
  declare tokens: Token[]
33✔
23
  declare tokens_meta: Array<StateTokenMeta | undefined>
33✔
24

33✔
25
  pos = 0
33✔
26
  declare posMax: number
2,097✔
27
  level = 0
2,097✔
28
  pending = ''
2,097✔
29
  pendingLevel = 0
2,097✔
30

2,097✔
31
  // Stores { start: end } pairs. Useful for backtrack
2,097✔
32
  // optimization of pairs parse (emphasis, strikes).
2,097✔
33
  cache: Record<number, number> = {}
2,097✔
34

2,097✔
35
  // backtick length => last seen position
2,097✔
36
  backticks: Record<number, number> = {}
2,097✔
37
  backticksScanned = false
2,097✔
38

2,097✔
39
  // Counter used to disable inline linkify-it execution
2,097✔
40
  // inside <a> and markdown links
2,097✔
41
  linkLevel = 0
2,097✔
42

2,097✔
43
  // List of emphasis-like delimiters for current tag
2,097✔
44
  delimiters: Delimiter[] = []
2,097✔
45

2,097✔
46
  // Stack of delimiter lists for upper level tags
2,097✔
47
  _prev_delimiters: Delimiter[][] = []
2,097✔
48

2,097✔
49
  // re-export Token class to use in block rules
2,097✔
50
  Token = Token
2,097✔
51

33✔
52
  constructor (src: string, md: MarkdownIt, env: Env, outTokens: Token[]) {
33✔
53
    this.src = src
2,097✔
54
    this.env = env
2,097✔
55
    this.md = md
2,097✔
56
    this.tokens = outTokens
2,097✔
57
    this.tokens_meta = Array(outTokens.length)
2,097✔
58

2,097✔
59
    this.posMax = this.src.length
2,097✔
60
  }
2,097✔
61

33✔
62
  // Flush pending text
33✔
63
  //
33✔
64
  pushPending (): Token {
33✔
65
    const token = new Token('text', '', 0)
424,389✔
66
    token.content = this.pending
424,389✔
67
    token.level = this.pendingLevel
424,389✔
68
    this.tokens.push(token)
424,389✔
69
    this.pending = ''
424,389✔
70
    return token
424,389✔
71
  }
424,389✔
72

33✔
73
  // Push new token to "stream".
33✔
74
  // If pending text exists - flush it as text token
33✔
75
  //
33✔
76
  push (type: string, tag: string, nesting: -1 | 0 | 1): Token {
33✔
77
    if (this.pending) {
813,916✔
78
      this.pushPending()
422,500✔
79
    }
422,500✔
80

813,916✔
81
    const token = new Token(type, tag, nesting)
813,916✔
82
    let token_meta = undefined
813,916✔
83

813,916✔
84
    if (nesting < 0) {
813,916✔
85
      // closing tag
20,303✔
86
      this.level--
20,303✔
87
      this.delimiters = this._prev_delimiters.pop()!
20,303✔
88
    }
20,303✔
89

813,916✔
90
    token.level = this.level
813,916✔
91

813,916✔
92
    if (nesting > 0) {
813,916✔
93
      // opening tag
20,303✔
94
      this.level++
20,303✔
95
      this._prev_delimiters.push(this.delimiters)
20,303✔
96
      this.delimiters = []
20,303✔
97
      token_meta = { delimiters: this.delimiters }
20,303✔
98
    }
20,303✔
99

813,916✔
100
    this.pendingLevel = this.level
813,916✔
101
    this.tokens.push(token)
813,916✔
102
    this.tokens_meta.push(token_meta)
813,916✔
103
    return token
813,916✔
104
  }
813,916✔
105

33✔
106
  // Scan a sequence of emphasis-like markers, and determine whether
33✔
107
  // it can start an emphasis sequence or end an emphasis sequence.
33✔
108
  //
33✔
109
  //  - start - position to scan from (it should point at a valid marker);
33✔
110
  //  - canSplitWord - determine if these markers can be found inside a word
33✔
111
  //
33✔
112
  scanDelims (start: number, canSplitWord: boolean): ScannedDelimiters {
33✔
113
    const max = this.posMax
470,765✔
114
    const marker = this.src.charCodeAt(start)
470,765✔
115

470,765✔
116
    // Astral characters below are combined manually, because .codePointAt()
470,765✔
117
    // does not guarantee numeric type output. And we don't wish JIT cache issues.
470,765✔
118
    // The broken surrogate pairs are evaluated as U+FFFD to prevent possible
470,765✔
119
    // crashes.
470,765✔
120

470,765✔
121
    let lastChar
470,765✔
122
    if (start === 0) {
470,765✔
123
      // treat beginning of the line as a whitespace
175✔
124
      lastChar = 0x20
175✔
125
    } else if (start === 1) {
470,764✔
126
      lastChar = this.src.charCodeAt(0)
27✔
127
      if ((lastChar & 0xF800) === 0xD800) { lastChar = 0xFFFD }
27!
128
    } else {
470,590✔
129
      lastChar = this.src.charCodeAt(start - 1)
470,563✔
130
      if ((lastChar & 0xFC00) === 0xDC00) {
470,563✔
131
        // low surrogate => add high one, replace broken pair with U+FFFD
2✔
132
        const highSurr = this.src.charCodeAt(start - 2)
2✔
133
        lastChar = (highSurr & 0xFC00) === 0xD800
2✔
134
          ? 0x10000 + ((highSurr - 0xD800) << 10) + (lastChar - 0xDC00)
2✔
135
          : 0xFFFD
2!
136
      } else if ((lastChar & 0xFC00) === 0xD800) {
470,563!
137
        lastChar = 0xFFFD
×
138
      }
×
139
    }
470,563✔
140

470,765✔
141
    let pos = start
470,765✔
142
    while (pos < max && this.src.charCodeAt(pos) === marker) { pos++ }
470,765✔
143

470,765✔
144
    const count = pos - start
470,765✔
145

470,765✔
146
    // treat end of the line as a whitespace
470,765✔
147
    let nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20
470,765✔
148
    if ((nextChar & 0xFC00) === 0xD800) {
470,765✔
149
      // high surrogate => add low one, replace broken pair with U+FFFD
2✔
150
      const lowSurr = this.src.charCodeAt(pos + 1)
2✔
151
      nextChar = (lowSurr & 0xFC00) === 0xDC00
2✔
152
        ? 0x10000 + ((nextChar - 0xD800) << 10) + (lowSurr - 0xDC00)
2✔
153
        : 0xFFFD
2!
154
    } else if ((nextChar & 0xFC00) === 0xDC00) {
470,765!
155
      nextChar = 0xFFFD
×
156
    }
×
157

470,765✔
158
    const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctCharCode(lastChar)
470,765✔
159
    const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctCharCode(nextChar)
470,765✔
160

470,765✔
161
    const isLastWhiteSpace = isWhiteSpace(lastChar)
470,765✔
162
    const isNextWhiteSpace = isWhiteSpace(nextChar)
470,765✔
163

470,765✔
164
    const left_flanking =
470,765✔
165
      !isNextWhiteSpace && (!isNextPunctChar || isLastWhiteSpace || isLastPunctChar)
470,765✔
166
    const right_flanking =
470,765✔
167
      !isLastWhiteSpace && (!isLastPunctChar || isNextWhiteSpace || isNextPunctChar)
470,765✔
168

470,765✔
169
    const can_open = left_flanking && (canSplitWord || !right_flanking || isLastPunctChar)
470,765✔
170
    const can_close = right_flanking && (canSplitWord || !left_flanking || isNextPunctChar)
470,765✔
171

470,765✔
172
    return { can_open, can_close, length: count }
470,765✔
173
  }
470,765✔
174
}
33✔
175

33✔
176
export default StateInline
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