@incremark/core 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,275 @@
1
+ /**
2
+ * @file GFM 脚注扩展的增量解析补丁
3
+ *
4
+ * @description
5
+ * GFM 脚注扩展会检查 parser.gfmFootnotes 来验证定义是否存在。
6
+ * 在增量解析场景下,定义可能在引用之后才出现,导致引用无法被正确解析。
7
+ *
8
+ * 本补丁移除定义检查,使脚注引用总是被解析为 footnoteReference。
9
+ *
10
+ * @module micromark-gfm-footnote-incremental
11
+ *
12
+ * @features
13
+ * - ✅ 移除脚注引用的定义检查(支持前向引用)
14
+ * - ✅ 覆盖 text[91] (`[`) 和 text[93] (`]`) 的处理
15
+ * - ✅ 延迟验证:解析时不检查定义是否存在
16
+ */
17
+
18
+ import type { Extension, Code, State, TokenizeContext, Tokenizer } from 'micromark-util-types'
19
+ import { gfmFootnote } from 'micromark-extension-gfm-footnote'
20
+ import { normalizeIdentifier } from 'micromark-util-normalize-identifier'
21
+ import { codes, constants } from 'micromark-util-symbol'
22
+ import { markdownLineEndingOrSpace } from 'micromark-util-character'
23
+
24
+ /**
25
+ * 创建支持增量解析的 GFM 脚注扩展
26
+ *
27
+ * 这个扩展基于官方 gfmFootnote(),但移除了定义检查,支持前向引用
28
+ *
29
+ * @returns Micromark 扩展对象
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { gfmFootnoteIncremental } from './micromark-gfm-footnote-incremental'
34
+ *
35
+ * const extensions = [
36
+ * gfm(),
37
+ * micromarkReferenceExtension(),
38
+ * gfmFootnoteIncremental() // 最后添加,确保覆盖
39
+ * ]
40
+ * ```
41
+ */
42
+ export function gfmFootnoteIncremental(): Extension {
43
+ const original = gfmFootnote()
44
+
45
+ return {
46
+ ...original,
47
+ text: {
48
+ ...original.text,
49
+ // 覆盖 text[91] (`[` 的处理) - 这是脚注引用解析的起点
50
+ [codes.leftSquareBracket]: {
51
+ ...original.text![codes.leftSquareBracket],
52
+ tokenize: tokenizeGfmFootnoteCallIncremental
53
+ },
54
+ // 覆盖 text[93] (`]` 的处理) - 用于处理 ![^1] 这样的情况
55
+ [codes.rightSquareBracket]: {
56
+ ...original.text![codes.rightSquareBracket],
57
+ tokenize: tokenizePotentialGfmFootnoteCallIncremental
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Tokenize 脚注引用 `[^id]`,移除定义检查
65
+ *
66
+ * 🔑 关键修改:不检查 parser.gfmFootnotes,总是允许解析脚注引用
67
+ *
68
+ * @param effects - Token 生成器
69
+ * @param ok - 成功时的状态函数
70
+ * @param nok - 失败时的状态函数
71
+ * @returns 起始状态函数
72
+ */
73
+ function tokenizeGfmFootnoteCallIncremental(
74
+ this: TokenizeContext,
75
+ effects: Parameters<Tokenizer>[0],
76
+ ok: State,
77
+ nok: State
78
+ ): State {
79
+ const self = this
80
+ let size = 0
81
+ let data = false
82
+
83
+ return start
84
+
85
+ /**
86
+ * 脚注引用起始位置
87
+ *
88
+ * ```markdown
89
+ * > | a [^b] c
90
+ * ^
91
+ * ```
92
+ */
93
+ function start(code: Code): State | undefined {
94
+ if (code !== codes.leftSquareBracket) {
95
+ return nok(code)
96
+ }
97
+
98
+ effects.enter('gfmFootnoteCall')
99
+ effects.enter('gfmFootnoteCallLabelMarker')
100
+ effects.consume(code)
101
+ effects.exit('gfmFootnoteCallLabelMarker')
102
+ return callStart
103
+ }
104
+
105
+ /**
106
+ * 在 `[` 之后,期望 `^`
107
+ *
108
+ * ```markdown
109
+ * > | a [^b] c
110
+ * ^
111
+ * ```
112
+ */
113
+ function callStart(code: Code): State | undefined {
114
+ if (code !== codes.caret) {
115
+ return nok(code)
116
+ }
117
+
118
+ effects.enter('gfmFootnoteCallMarker')
119
+ effects.consume(code)
120
+ effects.exit('gfmFootnoteCallMarker')
121
+ effects.enter('gfmFootnoteCallString')
122
+ const token = effects.enter('chunkString')
123
+ token.contentType = 'string'
124
+ return callData
125
+ }
126
+
127
+ /**
128
+ * 在脚注标识符中
129
+ *
130
+ * ```markdown
131
+ * > | a [^b] c
132
+ * ^
133
+ * ```
134
+ */
135
+ function callData(code: Code): State | undefined {
136
+ if (
137
+ // 太长
138
+ size > constants.linkReferenceSizeMax ||
139
+ // 右括号但没有数据
140
+ (code === codes.rightSquareBracket && !data) ||
141
+ // EOF、换行、空格、制表符、左括号不支持
142
+ code === codes.eof ||
143
+ code === codes.leftSquareBracket ||
144
+ markdownLineEndingOrSpace(code)
145
+ ) {
146
+ return nok(code)
147
+ }
148
+
149
+ if (code === codes.rightSquareBracket) {
150
+ effects.exit('chunkString')
151
+ effects.exit('gfmFootnoteCallString')
152
+
153
+ // 🔑 关键修改:移除定义检查
154
+ // 原始代码:
155
+ // const token = effects.exit('gfmFootnoteCallString')
156
+ // if (!defined.includes(normalizeIdentifier(self.sliceSerialize(token)))) {
157
+ // return nok(code)
158
+ // }
159
+
160
+ effects.enter('gfmFootnoteCallLabelMarker')
161
+ effects.consume(code)
162
+ effects.exit('gfmFootnoteCallLabelMarker')
163
+ effects.exit('gfmFootnoteCall')
164
+ return ok
165
+ }
166
+
167
+ if (!markdownLineEndingOrSpace(code)) {
168
+ data = true
169
+ }
170
+
171
+ size++
172
+ effects.consume(code)
173
+ return code === codes.backslash ? callEscape : callData
174
+ }
175
+
176
+ /**
177
+ * 在转义字符之后
178
+ *
179
+ * ```markdown
180
+ * > | a [^b\c] d
181
+ * ^
182
+ * ```
183
+ */
184
+ function callEscape(code: Code): State | undefined {
185
+ if (
186
+ code === codes.leftSquareBracket ||
187
+ code === codes.backslash ||
188
+ code === codes.rightSquareBracket
189
+ ) {
190
+ effects.consume(code)
191
+ size++
192
+ return callData
193
+ }
194
+
195
+ return callData(code)
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Tokenize 潜在的脚注引用 `![^id]`,移除定义检查
201
+ *
202
+ * 用于处理图片标记后的脚注引用(虽然这不是标准语法,但 GFM 会尝试解析)
203
+ *
204
+ * @param effects - Token 生成器
205
+ * @param ok - 成功时的状态函数
206
+ * @param nok - 失败时的状态函数
207
+ * @returns 起始状态函数
208
+ */
209
+ function tokenizePotentialGfmFootnoteCallIncremental(
210
+ this: TokenizeContext,
211
+ effects: Parameters<Tokenizer>[0],
212
+ ok: State,
213
+ nok: State
214
+ ): State {
215
+ const self = this
216
+ let index = self.events.length
217
+ let labelStart: any
218
+
219
+ // 查找开始的 labelImage token
220
+ while (index--) {
221
+ const token = self.events[index][1]
222
+ if (token.type === 'labelImage') {
223
+ labelStart = token
224
+ break
225
+ }
226
+
227
+ // 如果走得太远就退出
228
+ if (
229
+ token.type === 'gfmFootnoteCall' ||
230
+ token.type === 'labelLink' ||
231
+ token.type === 'label' ||
232
+ token.type === 'image' ||
233
+ token.type === 'link'
234
+ ) {
235
+ break
236
+ }
237
+ }
238
+
239
+ return start
240
+
241
+ function start(code: Code): State | undefined {
242
+ if (code !== codes.rightSquareBracket) {
243
+ return nok(code)
244
+ }
245
+
246
+ if (!labelStart || !labelStart._balanced) {
247
+ return nok(code)
248
+ }
249
+
250
+ const id = normalizeIdentifier(
251
+ self.sliceSerialize({
252
+ start: labelStart.end,
253
+ end: self.now()
254
+ })
255
+ )
256
+
257
+ // 只检查是否以 ^ 开头,不检查定义是否存在
258
+ if (id.codePointAt(0) !== codes.caret) {
259
+ return nok(code)
260
+ }
261
+
262
+ // 🔑 关键修改:移除定义检查
263
+ // 原始代码:
264
+ // const defined = self.parser.gfmFootnotes || (self.parser.gfmFootnotes = [])
265
+ // if (!defined.includes(id.slice(1))) {
266
+ // return nok(code)
267
+ // }
268
+
269
+ effects.enter('gfmFootnoteCallLabelMarker')
270
+ effects.consume(code)
271
+ effects.exit('gfmFootnoteCallLabelMarker')
272
+ return ok(code)
273
+ }
274
+ }
275
+