@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.
- 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
package/src/index.ts
CHANGED
|
@@ -82,6 +82,39 @@ export type {
|
|
|
82
82
|
TextNodeWithChunks
|
|
83
83
|
} from './transformer'
|
|
84
84
|
|
|
85
|
+
// HTML 树扩展
|
|
86
|
+
export {
|
|
87
|
+
// 核心转换器
|
|
88
|
+
transformHtmlNodes,
|
|
89
|
+
createHtmlTreeTransformer,
|
|
90
|
+
// 解析工具
|
|
91
|
+
parseHtmlTag,
|
|
92
|
+
parseHtmlFragment,
|
|
93
|
+
detectHtmlContentType,
|
|
94
|
+
// 类型守卫
|
|
95
|
+
isHtmlElementNode,
|
|
96
|
+
// 遍历工具
|
|
97
|
+
walkHtmlElements,
|
|
98
|
+
findHtmlElementsByTag,
|
|
99
|
+
// 序列化
|
|
100
|
+
htmlElementToString,
|
|
101
|
+
// 黑名单常量
|
|
102
|
+
HTML_TAG_BLACKLIST,
|
|
103
|
+
HTML_ATTR_BLACKLIST,
|
|
104
|
+
HTML_PROTOCOL_BLACKLIST,
|
|
105
|
+
// mdast 扩展
|
|
106
|
+
htmlTreeExtension
|
|
107
|
+
} from './extensions/html-extension'
|
|
108
|
+
|
|
109
|
+
// HTML 扩展类型
|
|
110
|
+
export type {
|
|
111
|
+
HtmlElementNode,
|
|
112
|
+
HtmlAttrInfo,
|
|
113
|
+
ParsedHtmlTag,
|
|
114
|
+
HtmlTreeExtensionOptions,
|
|
115
|
+
HtmlContentType
|
|
116
|
+
} from './extensions/html-extension'
|
|
117
|
+
|
|
85
118
|
/**
|
|
86
119
|
* BlockTransformer 动画样式
|
|
87
120
|
*
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 脚注解析测试
|
|
3
|
+
*
|
|
4
|
+
* 测试增量解析场景下脚注的正确处理,特别是多行脚注的稳定性判断
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest'
|
|
8
|
+
import { IncremarkParser } from './IncremarkParser'
|
|
9
|
+
import type { FootnoteDefinition } from 'mdast'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 辅助函数:从 AST 中提取所有脚注定义
|
|
13
|
+
*/
|
|
14
|
+
function extractFootnoteDefinitions(parser: IncremarkParser): FootnoteDefinition[] {
|
|
15
|
+
const ast = parser.getAst()
|
|
16
|
+
return ast.children.filter(
|
|
17
|
+
(node): node is FootnoteDefinition => node.type === 'footnoteDefinition'
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('脚注解析 - 基础场景', () => {
|
|
22
|
+
it('应该正确解析简单脚注', () => {
|
|
23
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
24
|
+
const markdown = `这是一个脚注引用[^1]。
|
|
25
|
+
|
|
26
|
+
[^1]: 这是脚注内容。`
|
|
27
|
+
|
|
28
|
+
parser.append(markdown)
|
|
29
|
+
const result = parser.finalize()
|
|
30
|
+
|
|
31
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
32
|
+
expect(footnotes).toHaveLength(1)
|
|
33
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
34
|
+
expect(footnotes[0].children).toHaveLength(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('应该正确解析多个脚注', () => {
|
|
38
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
39
|
+
const markdown = `第一个[^a]和第二个[^b]脚注。
|
|
40
|
+
|
|
41
|
+
[^a]: 脚注 A 的内容。
|
|
42
|
+
|
|
43
|
+
[^b]: 脚注 B 的内容。`
|
|
44
|
+
|
|
45
|
+
parser.append(markdown)
|
|
46
|
+
const result = parser.finalize()
|
|
47
|
+
|
|
48
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
49
|
+
expect(footnotes).toHaveLength(2)
|
|
50
|
+
expect(footnotes[0].identifier).toBe('a')
|
|
51
|
+
expect(footnotes[1].identifier).toBe('b')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('应该正确解析连续的脚注定义', () => {
|
|
55
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
56
|
+
const markdown = `[^1]: 内容 1
|
|
57
|
+
[^2]: 内容 2
|
|
58
|
+
[^3]: 内容 3`
|
|
59
|
+
|
|
60
|
+
parser.append(markdown)
|
|
61
|
+
const result = parser.finalize()
|
|
62
|
+
|
|
63
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
64
|
+
expect(footnotes).toHaveLength(3)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('脚注解析 - 多行脚注(关键测试)', () => {
|
|
69
|
+
it('应该正确解析带缩进的多行脚注', () => {
|
|
70
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
71
|
+
const markdown = `[^1]: 第一行
|
|
72
|
+
第二行(缩进)
|
|
73
|
+
第三行(缩进)`
|
|
74
|
+
|
|
75
|
+
parser.append(markdown)
|
|
76
|
+
const result = parser.finalize()
|
|
77
|
+
|
|
78
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
79
|
+
expect(footnotes).toHaveLength(1)
|
|
80
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
81
|
+
// 多行内容应该合并到一个段落中
|
|
82
|
+
expect(footnotes[0].children.length).toBeGreaterThan(0)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('应该正确处理脚注中的空行', () => {
|
|
86
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
87
|
+
const markdown = `[^1]: 第一段
|
|
88
|
+
|
|
89
|
+
第二段(缩进)`
|
|
90
|
+
|
|
91
|
+
parser.append(markdown)
|
|
92
|
+
const result = parser.finalize()
|
|
93
|
+
|
|
94
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
95
|
+
expect(footnotes).toHaveLength(1)
|
|
96
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
97
|
+
// 应该包含两个段落
|
|
98
|
+
expect(footnotes[0].children.length).toBeGreaterThanOrEqual(1)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('应该正确处理脚注中的列表', () => {
|
|
102
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
103
|
+
const markdown = `[^1]: 说明
|
|
104
|
+
|
|
105
|
+
- 列表项 1
|
|
106
|
+
- 列表项 2`
|
|
107
|
+
|
|
108
|
+
parser.append(markdown)
|
|
109
|
+
const result = parser.finalize()
|
|
110
|
+
|
|
111
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
112
|
+
expect(footnotes).toHaveLength(1)
|
|
113
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('应该正确处理脚注中的代码块(8空格缩进)', () => {
|
|
117
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
118
|
+
const markdown = `[^1]: 说明
|
|
119
|
+
|
|
120
|
+
代码块内容
|
|
121
|
+
第二行代码`
|
|
122
|
+
|
|
123
|
+
parser.append(markdown)
|
|
124
|
+
const result = parser.finalize()
|
|
125
|
+
|
|
126
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
127
|
+
expect(footnotes).toHaveLength(1)
|
|
128
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('脚注解析 - 增量解析(稳定性测试)', () => {
|
|
133
|
+
it('脚注第一行结束后不应该立即标记为 completed', () => {
|
|
134
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
135
|
+
|
|
136
|
+
// 第一步:添加脚注第一行
|
|
137
|
+
const update1 = parser.append('[^1]: 第一行\n')
|
|
138
|
+
// 应该在 pending 中,不应该在 completed 中
|
|
139
|
+
expect(update1.pending.length).toBeGreaterThan(0)
|
|
140
|
+
|
|
141
|
+
// 第二步:添加缩进的第二行
|
|
142
|
+
const update2 = parser.append(' 第二行\n')
|
|
143
|
+
|
|
144
|
+
// 第三步:添加空行结束
|
|
145
|
+
const update3 = parser.append('\n')
|
|
146
|
+
|
|
147
|
+
const result = parser.finalize()
|
|
148
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
149
|
+
|
|
150
|
+
expect(footnotes).toHaveLength(1)
|
|
151
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('遇到非缩进行时应该正确结束脚注', () => {
|
|
155
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
156
|
+
|
|
157
|
+
parser.append('[^1]: 第一行\n')
|
|
158
|
+
parser.append(' 第二行\n')
|
|
159
|
+
const update3 = parser.append('普通段落\n')
|
|
160
|
+
|
|
161
|
+
// 此时脚注应该已经完成
|
|
162
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
163
|
+
expect(footnotes).toHaveLength(1)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('遇到新脚注定义时应该结束前一个脚注', () => {
|
|
167
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
168
|
+
|
|
169
|
+
parser.append('[^1]: 第一行\n')
|
|
170
|
+
parser.append(' 第二行\n')
|
|
171
|
+
parser.append('[^2]: 新脚注\n')
|
|
172
|
+
|
|
173
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
174
|
+
expect(footnotes).toHaveLength(2)
|
|
175
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
176
|
+
expect(footnotes[1].identifier).toBe('2')
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('逐行解析多行脚注应该保持一致性', () => {
|
|
180
|
+
const markdown = `[^test]: 第一行
|
|
181
|
+
第二行
|
|
182
|
+
|
|
183
|
+
第三段
|
|
184
|
+
|
|
185
|
+
普通段落`
|
|
186
|
+
|
|
187
|
+
// 一次性解析
|
|
188
|
+
const parser1 = new IncremarkParser({ gfm: true })
|
|
189
|
+
parser1.append(markdown)
|
|
190
|
+
const result1 = parser1.finalize()
|
|
191
|
+
const footnotes1 = extractFootnoteDefinitions(parser1)
|
|
192
|
+
|
|
193
|
+
// 逐行解析
|
|
194
|
+
const parser2 = new IncremarkParser({ gfm: true })
|
|
195
|
+
const lines = markdown.split('\n')
|
|
196
|
+
lines.forEach((line, i) => {
|
|
197
|
+
parser2.append(line + (i < lines.length - 1 ? '\n' : ''))
|
|
198
|
+
})
|
|
199
|
+
const result2 = parser2.finalize()
|
|
200
|
+
const footnotes2 = extractFootnoteDefinitions(parser2)
|
|
201
|
+
|
|
202
|
+
// 结果应该一致
|
|
203
|
+
expect(footnotes1).toHaveLength(footnotes2.length)
|
|
204
|
+
expect(footnotes1[0].identifier).toBe(footnotes2[0].identifier)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('脚注解析 - 边界情况', () => {
|
|
209
|
+
it('应该正确处理脚注后的普通段落', () => {
|
|
210
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
211
|
+
const markdown = `[^1]: 脚注内容
|
|
212
|
+
|
|
213
|
+
普通段落(无缩进)`
|
|
214
|
+
|
|
215
|
+
parser.append(markdown)
|
|
216
|
+
const result = parser.finalize()
|
|
217
|
+
|
|
218
|
+
expect(result.ast.children).toHaveLength(2)
|
|
219
|
+
expect(result.ast.children[0].type).toBe('footnoteDefinition')
|
|
220
|
+
expect(result.ast.children[1].type).toBe('paragraph')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('应该正确处理最后一行是脚注开始的情况', () => {
|
|
224
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
225
|
+
parser.append('[^1]: 开始')
|
|
226
|
+
|
|
227
|
+
const result = parser.finalize()
|
|
228
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
229
|
+
|
|
230
|
+
expect(footnotes).toHaveLength(1)
|
|
231
|
+
expect(footnotes[0].identifier).toBe('1')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('应该区分脚注延续和独立的缩进代码块', () => {
|
|
235
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
236
|
+
const markdown = `普通段落
|
|
237
|
+
|
|
238
|
+
独立的缩进代码块
|
|
239
|
+
不属于脚注`
|
|
240
|
+
|
|
241
|
+
parser.append(markdown)
|
|
242
|
+
const result = parser.finalize()
|
|
243
|
+
|
|
244
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
245
|
+
expect(footnotes).toHaveLength(0)
|
|
246
|
+
// 应该被解析为代码块,而不是脚注
|
|
247
|
+
const codeBlock = result.ast.children.find(node => node.type === 'code')
|
|
248
|
+
expect(codeBlock).toBeDefined()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('应该正确处理空脚注标识符', () => {
|
|
252
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
253
|
+
// 注意:[^]: 不是有效的脚注语法
|
|
254
|
+
parser.append('[^]: 无效脚注')
|
|
255
|
+
|
|
256
|
+
const result = parser.finalize()
|
|
257
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
258
|
+
|
|
259
|
+
// 应该不被识别为脚注
|
|
260
|
+
expect(footnotes).toHaveLength(0)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('应该正确处理脚注标识符中的特殊字符', () => {
|
|
264
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
265
|
+
const markdown = `[^foo-bar_123]: 脚注内容`
|
|
266
|
+
|
|
267
|
+
parser.append(markdown)
|
|
268
|
+
const result = parser.finalize()
|
|
269
|
+
|
|
270
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
271
|
+
expect(footnotes).toHaveLength(1)
|
|
272
|
+
expect(footnotes[0].identifier).toBe('foo-bar_123')
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
describe('脚注解析 - 复杂场景', () => {
|
|
277
|
+
it('应该正确处理复杂的嵌套内容', () => {
|
|
278
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
279
|
+
const markdown = `[^complex]: 第一段
|
|
280
|
+
|
|
281
|
+
- 列表项 1
|
|
282
|
+
嵌套内容
|
|
283
|
+
- 列表项 2
|
|
284
|
+
|
|
285
|
+
\`\`\`js
|
|
286
|
+
代码块
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
最后一段`
|
|
290
|
+
|
|
291
|
+
parser.append(markdown)
|
|
292
|
+
const result = parser.finalize()
|
|
293
|
+
|
|
294
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
295
|
+
expect(footnotes).toHaveLength(1)
|
|
296
|
+
expect(footnotes[0].identifier).toBe('complex')
|
|
297
|
+
// 应该包含多个子元素
|
|
298
|
+
expect(footnotes[0].children.length).toBeGreaterThan(1)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('应该正确处理混合内容的文档', () => {
|
|
302
|
+
const parser = new IncremarkParser({ gfm: true })
|
|
303
|
+
const markdown = `# 标题
|
|
304
|
+
|
|
305
|
+
普通段落[^1]。
|
|
306
|
+
|
|
307
|
+
[^1]: 脚注内容
|
|
308
|
+
第二行
|
|
309
|
+
|
|
310
|
+
## 二级标题
|
|
311
|
+
|
|
312
|
+
另一个段落[^2]。
|
|
313
|
+
|
|
314
|
+
[^2]: 另一个脚注
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
最后的段落。`
|
|
319
|
+
|
|
320
|
+
parser.append(markdown)
|
|
321
|
+
const result = parser.finalize()
|
|
322
|
+
|
|
323
|
+
const footnotes = extractFootnoteDefinitions(parser)
|
|
324
|
+
expect(footnotes).toHaveLength(2)
|
|
325
|
+
|
|
326
|
+
// 验证其他元素也被正确解析
|
|
327
|
+
const headings = result.ast.children.filter(node => node.type === 'heading')
|
|
328
|
+
expect(headings).toHaveLength(2)
|
|
329
|
+
|
|
330
|
+
const paragraphs = result.ast.children.filter(node => node.type === 'paragraph')
|
|
331
|
+
expect(paragraphs.length).toBeGreaterThan(0)
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|