@peaceroad/markdown-it-strong-ja 0.7.2 → 0.8.1

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,475 @@
1
+ import { buildLinkCloseMap } from '../token-link-utils.js'
2
+ import {
3
+ isAsteriskEmphasisToken,
4
+ hasTextMarkerCharsInRange,
5
+ buildAsteriskWrapperPrefixStats,
6
+ shouldAttemptBrokenRefRewrite
7
+ } from './guards.js'
8
+ import {
9
+ BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE,
10
+ BROKEN_REF_FAST_PATH_RESULT_NO_MATCH,
11
+ applyBrokenRefTokenOnlyFastPath
12
+ } from './fastpaths.js'
13
+
14
+ const scanBrokenRefState = (text, out) => {
15
+ if (!text || text.indexOf('[') === -1) {
16
+ out.depth = 0
17
+ out.brokenEnd = false
18
+ out.tailOpen = -1
19
+ return out
20
+ }
21
+ let depth = 0
22
+ let lastOpen = -1
23
+ let lastClose = -1
24
+ for (let i = 0; i < text.length; i++) {
25
+ const ch = text.charCodeAt(i)
26
+ if (ch === 0x5B) {
27
+ depth++
28
+ lastOpen = i
29
+ } else if (ch === 0x5D) {
30
+ if (depth > 0) depth--
31
+ lastClose = i
32
+ }
33
+ }
34
+ out.depth = depth
35
+ out.brokenEnd = depth > 0 && lastOpen > lastClose
36
+ out.tailOpen = out.brokenEnd ? lastOpen : -1
37
+ return out
38
+ }
39
+
40
+ const resetBrokenRefScanState = (scanState) => {
41
+ if (!scanState) return scanState
42
+ scanState.depth = 0
43
+ scanState.brokenEnd = false
44
+ scanState.tailOpen = -1
45
+ return scanState
46
+ }
47
+
48
+ const updateBracketDepth = (text, depth) => {
49
+ if (!text || depth <= 0) return depth
50
+ for (let i = 0; i < text.length; i++) {
51
+ const ch = text.charCodeAt(i)
52
+ if (ch === 0x5B) {
53
+ depth++
54
+ } else if (ch === 0x5D) {
55
+ if (depth > 0) {
56
+ depth--
57
+ if (depth === 0) return 0
58
+ }
59
+ }
60
+ }
61
+ return depth
62
+ }
63
+
64
+ const createBrokenRefWrapperBalance = () => {
65
+ return {
66
+ strong: 0,
67
+ em: 0,
68
+ total: 0
69
+ }
70
+ }
71
+
72
+ const updateBrokenRefWrapperBalance = (token, balance) => {
73
+ if (!token || !token.type) return
74
+ if ((token.type === 'strong_open' || token.type === 'strong_close' || token.type === 'em_open' || token.type === 'em_close') &&
75
+ !isAsteriskEmphasisToken(token)) {
76
+ return
77
+ }
78
+ if (token.type === 'strong_open') {
79
+ balance.strong++
80
+ balance.total++
81
+ return
82
+ }
83
+ if (token.type === 'em_open') {
84
+ balance.em++
85
+ balance.total++
86
+ return
87
+ }
88
+ if (token.type === 'strong_close') {
89
+ if (balance.strong > 0) {
90
+ balance.strong--
91
+ balance.total--
92
+ }
93
+ return
94
+ }
95
+ if (token.type === 'em_close' && balance.em > 0) {
96
+ balance.em--
97
+ balance.total--
98
+ }
99
+ }
100
+
101
+ const expandSegmentEndForWrapperBalance = (tokens, startIdx, endIdx) => {
102
+ if (!tokens || startIdx < 0 || endIdx < startIdx) return endIdx
103
+ const balance = createBrokenRefWrapperBalance()
104
+ let expandedEnd = endIdx
105
+
106
+ for (let i = startIdx; i <= expandedEnd; i++) {
107
+ updateBrokenRefWrapperBalance(tokens[i], balance)
108
+ }
109
+
110
+ while (balance.total > 0 && expandedEnd + 1 < tokens.length) {
111
+ expandedEnd++
112
+ updateBrokenRefWrapperBalance(tokens[expandedEnd], balance)
113
+ }
114
+
115
+ return balance.total > 0 ? -1 : expandedEnd
116
+ }
117
+
118
+ const bumpBrokenRefMetric = (metrics, bucket, key) => {
119
+ if (!metrics || !bucket || !key) return
120
+ let table = metrics[bucket]
121
+ if (!table || typeof table !== 'object') {
122
+ table = Object.create(null)
123
+ metrics[bucket] = table
124
+ }
125
+ table[key] = (table[key] || 0) + 1
126
+ }
127
+
128
+ const ensureBrokenRefLinkCloseMap = (tokens, facts = null, hooks = null, fallbackCache = null) => {
129
+ if (hooks && typeof hooks.ensureLinkCloseMap === 'function') {
130
+ return hooks.ensureLinkCloseMap(facts, tokens)
131
+ }
132
+ if (fallbackCache && fallbackCache.linkCloseMap !== undefined) {
133
+ return fallbackCache.linkCloseMap
134
+ }
135
+ const linkCloseMap = (!tokens || tokens.length === 0)
136
+ ? new Map()
137
+ : buildLinkCloseMap(tokens, 0, tokens.length - 1)
138
+ if (fallbackCache) {
139
+ fallbackCache.linkCloseMap = linkCloseMap
140
+ }
141
+ return linkCloseMap
142
+ }
143
+
144
+ const ensureBrokenRefWrapperPrefixStats = (tokens, facts = null, hooks = null, fallbackCache = null) => {
145
+ if (hooks && typeof hooks.ensureWrapperPrefixStats === 'function') {
146
+ return hooks.ensureWrapperPrefixStats(facts, tokens)
147
+ }
148
+ if (fallbackCache && fallbackCache.wrapperPrefixStats !== undefined) {
149
+ return fallbackCache.wrapperPrefixStats
150
+ }
151
+ const wrapperPrefixStats = (!tokens || tokens.length === 0)
152
+ ? null
153
+ : buildAsteriskWrapperPrefixStats(tokens)
154
+ if (fallbackCache) {
155
+ fallbackCache.wrapperPrefixStats = wrapperPrefixStats
156
+ }
157
+ return wrapperPrefixStats
158
+ }
159
+
160
+ const invalidateBrokenRefDerivedCaches = (facts = null, hooks = null, fallbackCache = null) => {
161
+ if (fallbackCache) {
162
+ fallbackCache.linkCloseMap = undefined
163
+ fallbackCache.wrapperPrefixStats = undefined
164
+ }
165
+ if (hooks && typeof hooks.invalidateDerivedCaches === 'function') {
166
+ hooks.invalidateDerivedCaches(facts)
167
+ }
168
+ }
169
+
170
+ const markBrokenRefLevelRebuildFrom = (facts = null, startIdx = 0, hooks = null) => {
171
+ if (hooks && typeof hooks.markLevelRebuildFrom === 'function') {
172
+ hooks.markLevelRebuildFrom(facts, startIdx)
173
+ }
174
+ }
175
+
176
+ const BROKEN_REF_FLOW_SKIP_NO_TEXT_MARKER = 'skip-no-text-marker'
177
+ const BROKEN_REF_FLOW_SKIP_GUARD = 'skip-guard'
178
+ const BROKEN_REF_FLOW_SKIP_NO_ACTIVE_SIGNATURE = 'skip-no-active-signature'
179
+ const BROKEN_REF_FLOW_SKIP_NO_FASTPATH_MATCH = 'skip-no-fastpath-match'
180
+ const BROKEN_REF_FLOW_REPAIRED = 'repaired'
181
+
182
+ const resolveBrokenRefSegmentEnd = (children, brokenRefCandidate, closeIdx, metrics = null) => {
183
+ let segmentEnd = expandSegmentEndForWrapperBalance(children, brokenRefCandidate.start, closeIdx)
184
+ if (segmentEnd !== -1) return segmentEnd
185
+ bumpBrokenRefMetric(metrics, 'brokenRefFlow', 'wrapper-expand-fallback')
186
+ return closeIdx
187
+ }
188
+
189
+ const resolveBrokenRefCandidateGuardFlow = (
190
+ children,
191
+ brokenRefCandidate,
192
+ segmentEnd,
193
+ facts = null,
194
+ hooks = null,
195
+ fallbackCache = null
196
+ ) => {
197
+ if (!hasTextMarkerCharsInRange(children, brokenRefCandidate.start, segmentEnd, brokenRefCandidate.startTextOffset)) {
198
+ return BROKEN_REF_FLOW_SKIP_NO_TEXT_MARKER
199
+ }
200
+ const wrapperPrefixStats = ensureBrokenRefWrapperPrefixStats(children, facts, hooks, fallbackCache)
201
+ if (!shouldAttemptBrokenRefRewrite(
202
+ children,
203
+ brokenRefCandidate.start,
204
+ segmentEnd,
205
+ brokenRefCandidate.startTextOffset,
206
+ wrapperPrefixStats
207
+ )) {
208
+ return BROKEN_REF_FLOW_SKIP_GUARD
209
+ }
210
+ return null
211
+ }
212
+
213
+ const resolveBrokenRefFastPathFlow = (
214
+ children,
215
+ brokenRefCandidate,
216
+ segmentEnd,
217
+ linkCloseMap,
218
+ metrics = null
219
+ ) => {
220
+ const fastPathResult = applyBrokenRefTokenOnlyFastPath(
221
+ children,
222
+ brokenRefCandidate.start,
223
+ segmentEnd,
224
+ linkCloseMap,
225
+ metrics,
226
+ bumpBrokenRefMetric
227
+ )
228
+ if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE) {
229
+ return BROKEN_REF_FLOW_SKIP_NO_ACTIVE_SIGNATURE
230
+ }
231
+ if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_MATCH) {
232
+ return BROKEN_REF_FLOW_SKIP_NO_FASTPATH_MATCH
233
+ }
234
+ return BROKEN_REF_FLOW_REPAIRED
235
+ }
236
+
237
+ const runBrokenRefCandidateRewrite = (
238
+ children,
239
+ brokenRefCandidate,
240
+ closeIdx,
241
+ linkCloseMap,
242
+ metrics = null,
243
+ facts = null,
244
+ hooks = null,
245
+ fallbackCache = null
246
+ ) => {
247
+ const segmentEnd = resolveBrokenRefSegmentEnd(children, brokenRefCandidate, closeIdx, metrics)
248
+ const guardFlow = resolveBrokenRefCandidateGuardFlow(
249
+ children,
250
+ brokenRefCandidate,
251
+ segmentEnd,
252
+ facts,
253
+ hooks,
254
+ fallbackCache
255
+ )
256
+ if (guardFlow) return guardFlow
257
+ const fastPathFlow = resolveBrokenRefFastPathFlow(
258
+ children,
259
+ brokenRefCandidate,
260
+ segmentEnd,
261
+ linkCloseMap,
262
+ metrics
263
+ )
264
+ if (fastPathFlow !== BROKEN_REF_FLOW_REPAIRED) return fastPathFlow
265
+ invalidateBrokenRefDerivedCaches(facts, hooks, fallbackCache)
266
+ markBrokenRefLevelRebuildFrom(facts, brokenRefCandidate.start, hooks)
267
+ return BROKEN_REF_FLOW_REPAIRED
268
+ }
269
+
270
+ const resetBrokenRefCandidateState = (candidateState) => {
271
+ candidateState.start = -1
272
+ candidateState.depth = 0
273
+ candidateState.startTextOffset = 0
274
+ return candidateState
275
+ }
276
+
277
+ const startBrokenRefCandidateState = (candidateState, tokenIdx, scan) => {
278
+ candidateState.start = tokenIdx
279
+ candidateState.depth = scan.depth
280
+ candidateState.startTextOffset = scan.tailOpen > 0 ? scan.tailOpen : 0
281
+ return candidateState
282
+ }
283
+
284
+ const createBrokenRefSignalSeed = (facts = null) => {
285
+ return {
286
+ hasBracketText: !!(facts && facts.hasBracketText),
287
+ hasEmphasis: !!(facts && facts.hasEmphasis),
288
+ hasLinkClose: !!(facts && facts.hasLinkClose)
289
+ }
290
+ }
291
+
292
+ const createBrokenRefPassSignals = (seedSignals = null) => {
293
+ const seed = seedSignals || {}
294
+ return {
295
+ hasBracketText: !!seed.hasBracketText,
296
+ hasEmphasis: !!seed.hasEmphasis,
297
+ hasLinkClose: !!seed.hasLinkClose
298
+ }
299
+ }
300
+
301
+ const observeBrokenRefTextToken = (passSignals, candidateState, text, tokenIdx, scanState) => {
302
+ const hasOpenBracket = text.indexOf('[') !== -1
303
+ const hasCloseBracket = text.indexOf(']') !== -1
304
+ if (!passSignals.hasBracketText && (hasOpenBracket || hasCloseBracket)) {
305
+ passSignals.hasBracketText = true
306
+ }
307
+ if (candidateState.start === -1) {
308
+ if (!hasOpenBracket) return
309
+ const scan = scanBrokenRefState(text, scanState)
310
+ if (scan.brokenEnd) {
311
+ startBrokenRefCandidateState(candidateState, tokenIdx, scan)
312
+ }
313
+ return
314
+ }
315
+ if (!hasOpenBracket && !hasCloseBracket) return
316
+ candidateState.depth = updateBracketDepth(text, candidateState.depth)
317
+ if (candidateState.depth <= 0) {
318
+ resetBrokenRefCandidateState(candidateState)
319
+ }
320
+ }
321
+
322
+ const observeBrokenRefPassTokenFlags = (passSignals, child) => {
323
+ if (!passSignals.hasEmphasis && isAsteriskEmphasisToken(child)) {
324
+ passSignals.hasEmphasis = true
325
+ }
326
+ if (!passSignals.hasLinkClose && child.type === 'link_close') {
327
+ passSignals.hasLinkClose = true
328
+ }
329
+ }
330
+
331
+ const buildBrokenRefRepairPassResult = (didRepair, passSignals) => {
332
+ return {
333
+ didRepair,
334
+ hasBracketText: passSignals.hasBracketText,
335
+ hasEmphasis: passSignals.hasEmphasis,
336
+ hasLinkClose: passSignals.hasLinkClose
337
+ }
338
+ }
339
+
340
+ const collectBrokenRefPassSignals = (children, seedSignals = null) => {
341
+ const passSignals = createBrokenRefPassSignals(seedSignals)
342
+ if (!children || children.length === 0) return passSignals
343
+ for (let i = 0; i < children.length; i++) {
344
+ const child = children[i]
345
+ if (!child) continue
346
+ if (!passSignals.hasBracketText && child.type === 'text' && child.content) {
347
+ if (child.content.indexOf('[') !== -1 || child.content.indexOf(']') !== -1) {
348
+ passSignals.hasBracketText = true
349
+ }
350
+ }
351
+ observeBrokenRefPassTokenFlags(passSignals, child)
352
+ if (passSignals.hasBracketText && passSignals.hasEmphasis && passSignals.hasLinkClose) {
353
+ break
354
+ }
355
+ }
356
+ return passSignals
357
+ }
358
+
359
+ const tryRepairBrokenRefCandidateAtLinkOpen = (
360
+ children,
361
+ child,
362
+ childIdx,
363
+ brokenRefCandidate,
364
+ passSignals,
365
+ metrics = null,
366
+ facts = null,
367
+ hooks = null,
368
+ fallbackCache = null
369
+ ) => {
370
+ if (!child || child.type !== 'link_open' || brokenRefCandidate.start === -1) return null
371
+ if (brokenRefCandidate.depth <= 0) {
372
+ resetBrokenRefCandidateState(brokenRefCandidate)
373
+ return null
374
+ }
375
+ const linkCloseMap = ensureBrokenRefLinkCloseMap(children, facts, hooks, fallbackCache)
376
+ const closeIdx = linkCloseMap.get(childIdx) ?? -1
377
+ if (closeIdx === -1) return null
378
+ bumpBrokenRefMetric(metrics, 'brokenRefFlow', 'candidate')
379
+ const flowResult = runBrokenRefCandidateRewrite(
380
+ children,
381
+ brokenRefCandidate,
382
+ closeIdx,
383
+ linkCloseMap,
384
+ metrics,
385
+ facts,
386
+ hooks,
387
+ fallbackCache
388
+ )
389
+ if (flowResult !== BROKEN_REF_FLOW_REPAIRED) {
390
+ bumpBrokenRefMetric(metrics, 'brokenRefFlow', flowResult)
391
+ resetBrokenRefCandidateState(brokenRefCandidate)
392
+ return null
393
+ }
394
+ bumpBrokenRefMetric(metrics, 'brokenRefFlow', BROKEN_REF_FLOW_REPAIRED)
395
+ return buildBrokenRefRepairPassResult(true, passSignals)
396
+ }
397
+
398
+ const runBrokenRefRepairPass = (children, scanState, metrics = null, facts = null, hooks = null) => {
399
+ resetBrokenRefScanState(scanState)
400
+ const brokenRefCandidate = resetBrokenRefCandidateState({ start: -1, depth: 0, startTextOffset: 0 })
401
+ const passSignals = createBrokenRefPassSignals(createBrokenRefSignalSeed(facts))
402
+ const fallbackCache = {
403
+ linkCloseMap: undefined,
404
+ wrapperPrefixStats: undefined
405
+ }
406
+
407
+ for (let j = 0; j < children.length; j++) {
408
+ const child = children[j]
409
+ if (!child) continue
410
+
411
+ if (child.type === 'text' && child.content) {
412
+ observeBrokenRefTextToken(passSignals, brokenRefCandidate, child.content, j, scanState)
413
+ }
414
+
415
+ observeBrokenRefPassTokenFlags(passSignals, child)
416
+ const repaired = tryRepairBrokenRefCandidateAtLinkOpen(
417
+ children,
418
+ child,
419
+ j,
420
+ brokenRefCandidate,
421
+ passSignals,
422
+ metrics,
423
+ facts,
424
+ hooks,
425
+ fallbackCache
426
+ )
427
+ if (repaired) return repaired
428
+ }
429
+
430
+ return buildBrokenRefRepairPassResult(false, passSignals)
431
+ }
432
+
433
+ const computeMaxBrokenRefRepairPass = (children, scanState) => {
434
+ resetBrokenRefScanState(scanState)
435
+ let maxRepairPass = 0
436
+ for (let j = 0; j < children.length; j++) {
437
+ const child = children[j]
438
+ if (!child || child.type !== 'text' || !child.content) continue
439
+ if (child.content.indexOf('[') === -1) continue
440
+ if (scanBrokenRefState(child.content, scanState).brokenEnd) {
441
+ maxRepairPass++
442
+ }
443
+ }
444
+ return maxRepairPass
445
+ }
446
+
447
+ const runBrokenRefRepairs = (children, maxRepairPass, scanState, metrics = null, facts = null, hooks = null) => {
448
+ let repairPassCount = 0
449
+ let changed = false
450
+ while (repairPassCount < maxRepairPass) {
451
+ const pass = runBrokenRefRepairPass(children, scanState, metrics, facts, hooks)
452
+ if (!pass.didRepair) {
453
+ return {
454
+ changed,
455
+ hasBracketText: pass.hasBracketText,
456
+ hasEmphasis: pass.hasEmphasis,
457
+ hasLinkClose: pass.hasLinkClose
458
+ }
459
+ }
460
+ changed = true
461
+ repairPassCount++
462
+ }
463
+ const finalSignals = collectBrokenRefPassSignals(children, createBrokenRefSignalSeed(facts))
464
+ return {
465
+ changed,
466
+ hasBracketText: finalSignals.hasBracketText,
467
+ hasEmphasis: finalSignals.hasEmphasis,
468
+ hasLinkClose: finalSignals.hasLinkClose
469
+ }
470
+ }
471
+
472
+ export {
473
+ computeMaxBrokenRefRepairPass,
474
+ runBrokenRefRepairs
475
+ }