@incremark/core 0.1.1 → 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.
- package/dist/detector/index.d.ts +1 -1
- package/dist/detector/index.js +9 -1
- package/dist/detector/index.js.map +1 -1
- package/dist/index-3rgnFbip.d.ts +396 -0
- package/dist/index.d.ts +53 -2
- package/dist/index.js +1564 -35
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +6 -1
- package/dist/utils/index.js +7 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +7 -1
- package/src/__tests__/footnote.test.ts +214 -0
- package/src/detector/index.ts +30 -0
- package/src/extensions/html-extension/index.test.ts +409 -0
- package/src/extensions/html-extension/index.ts +792 -0
- package/src/extensions/micromark-gfm-footnote-incremental.ts +275 -0
- package/src/extensions/micromark-reference-extension.ts +724 -0
- package/src/index.ts +33 -0
- package/src/parser/IncremarkParser.footnote.test.ts +334 -0
- package/src/parser/IncremarkParser.ts +374 -14
- package/src/types/index.ts +29 -1
- package/src/utils/index.ts +9 -0
- package/dist/index-ChNeZ1wr.d.ts +0 -217
|
@@ -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
|
+
|