@peaceroad/markdown-it-strong-ja 0.7.2 → 0.8.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,733 @@
1
+ import Token from 'markdown-it/lib/token.mjs'
2
+ import { buildLinkCloseMap, convertCollapsedReferenceLinks, mergeBrokenMarksAroundLinks } from '../token-link-utils.js'
3
+ import {
4
+ rebuildInlineLevels,
5
+ fixEmOuterStrongSequence,
6
+ fixLeadingAsteriskEm,
7
+ fixTrailingStrong
8
+ } from '../token-core.js'
9
+ import {
10
+ getInlineWrapperBase,
11
+ getRuntimeOpt,
12
+ MODE_FLAG_COMPATIBLE,
13
+ MODE_FLAG_AGGRESSIVE,
14
+ MODE_FLAG_JAPANESE_PLUS,
15
+ MODE_FLAG_JAPANESE_ANY
16
+ } from '../token-utils.js'
17
+ import {
18
+ hasMarkerChars,
19
+ isAsteriskEmphasisToken,
20
+ hasJapaneseContextInRange,
21
+ hasEmphasisSignalInRange,
22
+ hasTextMarkerCharsInRange,
23
+ buildAsteriskWrapperPrefixStats,
24
+ shouldAttemptBrokenRefRewrite,
25
+ scanInlinePostprocessSignals
26
+ } from './guards.js'
27
+ import {
28
+ BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE,
29
+ BROKEN_REF_FAST_PATH_RESULT_NO_MATCH,
30
+ applyBrokenRefTokenOnlyFastPath,
31
+ tryFixTailPatternTokenOnly,
32
+ tryFixTailDanglingStrongCloseTokenOnly
33
+ } from './fastpaths.js'
34
+
35
+ const scanBrokenRefState = (text, out) => {
36
+ if (!text || text.indexOf('[') === -1) {
37
+ out.depth = 0
38
+ out.brokenEnd = false
39
+ out.tailOpen = -1
40
+ return out
41
+ }
42
+ let depth = 0
43
+ let lastOpen = -1
44
+ let lastClose = -1
45
+ for (let i = 0; i < text.length; i++) {
46
+ const ch = text.charCodeAt(i)
47
+ if (ch === 0x5B) {
48
+ depth++
49
+ lastOpen = i
50
+ } else if (ch === 0x5D) {
51
+ if (depth > 0) depth--
52
+ lastClose = i
53
+ }
54
+ }
55
+ out.depth = depth
56
+ out.brokenEnd = depth > 0 && lastOpen > lastClose
57
+ out.tailOpen = out.brokenEnd ? lastOpen : -1
58
+ return out
59
+ }
60
+
61
+ const resetBrokenRefScanState = (scanState) => {
62
+ if (!scanState) return scanState
63
+ scanState.depth = 0
64
+ scanState.brokenEnd = false
65
+ scanState.tailOpen = -1
66
+ return scanState
67
+ }
68
+
69
+ const updateBracketDepth = (text, depth) => {
70
+ if (!text || depth <= 0) return depth
71
+ for (let i = 0; i < text.length; i++) {
72
+ const ch = text.charCodeAt(i)
73
+ if (ch === 0x5B) {
74
+ depth++
75
+ } else if (ch === 0x5D) {
76
+ if (depth > 0) {
77
+ depth--
78
+ if (depth === 0) return 0
79
+ }
80
+ }
81
+ }
82
+ return depth
83
+ }
84
+
85
+ const expandSegmentEndForWrapperBalance = (tokens, startIdx, endIdx) => {
86
+ if (!tokens || startIdx < 0 || endIdx < startIdx) return endIdx
87
+ const depthMap = new Map()
88
+ let openDepthTotal = 0
89
+ let expandedEnd = endIdx
90
+
91
+ for (let i = startIdx; i <= expandedEnd; i++) {
92
+ const token = tokens[i]
93
+ if (!token || !token.type) continue
94
+ if ((token.type === 'strong_open' || token.type === 'strong_close' || token.type === 'em_open' || token.type === 'em_close') &&
95
+ !isAsteriskEmphasisToken(token)) {
96
+ continue
97
+ }
98
+ const base = getInlineWrapperBase(token.type)
99
+ if (!base) continue
100
+ if (token.type.endsWith('_open')) {
101
+ depthMap.set(base, (depthMap.get(base) || 0) + 1)
102
+ openDepthTotal++
103
+ continue
104
+ }
105
+ const prev = depthMap.get(base) || 0
106
+ if (prev > 0) {
107
+ depthMap.set(base, prev - 1)
108
+ openDepthTotal--
109
+ }
110
+ }
111
+
112
+ while (openDepthTotal > 0 && expandedEnd + 1 < tokens.length) {
113
+ expandedEnd++
114
+ const token = tokens[expandedEnd]
115
+ if (!token || !token.type) continue
116
+ if ((token.type === 'strong_open' || token.type === 'strong_close' || token.type === 'em_open' || token.type === 'em_close') &&
117
+ !isAsteriskEmphasisToken(token)) {
118
+ continue
119
+ }
120
+ const base = getInlineWrapperBase(token.type)
121
+ if (!base) continue
122
+ if (token.type.endsWith('_open')) {
123
+ depthMap.set(base, (depthMap.get(base) || 0) + 1)
124
+ openDepthTotal++
125
+ continue
126
+ }
127
+ const prev = depthMap.get(base) || 0
128
+ if (prev > 0) {
129
+ depthMap.set(base, prev - 1)
130
+ openDepthTotal--
131
+ }
132
+ }
133
+
134
+ return openDepthTotal > 0 ? -1 : expandedEnd
135
+ }
136
+
137
+ const fallbackMarkupByType = (type) => {
138
+ if (type === 'strong_open' || type === 'strong_close') return '**'
139
+ if (type === 'em_open' || type === 'em_close') return '*'
140
+ return ''
141
+ }
142
+
143
+ const makeTokenLiteralText = (token) => {
144
+ if (!token) return
145
+ const literal = token.markup || fallbackMarkupByType(token.type)
146
+ token.type = 'text'
147
+ token.tag = ''
148
+ token.nesting = 0
149
+ token.content = literal
150
+ token.markup = ''
151
+ token.info = ''
152
+ }
153
+
154
+ const sanitizeEmStrongBalance = (tokens) => {
155
+ if (!tokens || tokens.length === 0) return false
156
+ const stack = []
157
+ let changed = false
158
+ for (let i = 0; i < tokens.length; i++) {
159
+ const token = tokens[i]
160
+ if (!token || !token.type) continue
161
+ if (token.type === 'strong_open' || token.type === 'em_open') {
162
+ stack.push({ type: token.type, idx: i })
163
+ continue
164
+ }
165
+ if (token.type !== 'strong_close' && token.type !== 'em_close') continue
166
+ const expected = token.type === 'strong_close' ? 'strong_open' : 'em_open'
167
+ if (stack.length > 0 && stack[stack.length - 1].type === expected) {
168
+ stack.pop()
169
+ continue
170
+ }
171
+ makeTokenLiteralText(token)
172
+ changed = true
173
+ }
174
+ for (let i = stack.length - 1; i >= 0; i--) {
175
+ const entry = stack[i]
176
+ const token = tokens[entry.idx]
177
+ if (!token) continue
178
+ makeTokenLiteralText(token)
179
+ changed = true
180
+ }
181
+ return changed
182
+ }
183
+
184
+ const getPostprocessMetrics = (state) => {
185
+ if (!state || !state.env) return null
186
+ const metrics = state.env.__strongJaPostprocessMetrics
187
+ if (!metrics || typeof metrics !== 'object') return null
188
+ return metrics
189
+ }
190
+
191
+ const bumpPostprocessMetric = (metrics, bucket, key) => {
192
+ if (!metrics || !bucket || !key) return
193
+ let table = metrics[bucket]
194
+ if (!table || typeof table !== 'object') {
195
+ table = Object.create(null)
196
+ metrics[bucket] = table
197
+ }
198
+ table[key] = (table[key] || 0) + 1
199
+ }
200
+
201
+ const runBrokenRefRepairPass = (children, scanState, metrics = null) => {
202
+ resetBrokenRefScanState(scanState)
203
+ let wrapperPrefixStats = null
204
+ let brokenRefStart = -1
205
+ let brokenRefDepth = 0
206
+ let brokenRefStartTextOffset = 0
207
+ let linkCloseMap = null
208
+ let hasBracketText = false
209
+ let hasEmphasis = false
210
+ let hasLinkClose = false
211
+
212
+ for (let j = 0; j < children.length; j++) {
213
+ const child = children[j]
214
+ if (!child) continue
215
+
216
+ if (child.type === 'text' && child.content) {
217
+ const text = child.content
218
+ const hasOpenBracket = text.indexOf('[') !== -1
219
+ const hasCloseBracket = text.indexOf(']') !== -1
220
+ if (!hasBracketText && (hasOpenBracket || hasCloseBracket)) {
221
+ hasBracketText = true
222
+ }
223
+ if (brokenRefStart === -1) {
224
+ if (hasOpenBracket) {
225
+ const scan = scanBrokenRefState(text, scanState)
226
+ if (scan.brokenEnd) {
227
+ brokenRefStart = j
228
+ brokenRefDepth = scan.depth
229
+ brokenRefStartTextOffset = scan.tailOpen > 0 ? scan.tailOpen : 0
230
+ continue
231
+ }
232
+ }
233
+ } else if (hasOpenBracket || hasCloseBracket) {
234
+ brokenRefDepth = updateBracketDepth(text, brokenRefDepth)
235
+ if (brokenRefDepth <= 0) {
236
+ brokenRefStart = -1
237
+ brokenRefDepth = 0
238
+ brokenRefStartTextOffset = 0
239
+ }
240
+ }
241
+ }
242
+
243
+ if (!hasEmphasis && isAsteriskEmphasisToken(child)) {
244
+ hasEmphasis = true
245
+ }
246
+ if (!hasLinkClose && child.type === 'link_close') {
247
+ hasLinkClose = true
248
+ }
249
+ if (brokenRefStart === -1 || child.type !== 'link_open') continue
250
+ if (brokenRefDepth <= 0) {
251
+ brokenRefStart = -1
252
+ brokenRefDepth = 0
253
+ brokenRefStartTextOffset = 0
254
+ continue
255
+ }
256
+ if (linkCloseMap === null) {
257
+ linkCloseMap = buildLinkCloseMap(children, 0, children.length - 1)
258
+ }
259
+ const closeIdx = linkCloseMap.get(j) ?? -1
260
+ if (closeIdx === -1) continue
261
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'candidate')
262
+ let segmentEnd = expandSegmentEndForWrapperBalance(children, brokenRefStart, closeIdx)
263
+ if (segmentEnd === -1) {
264
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'wrapper-expand-fallback')
265
+ segmentEnd = closeIdx
266
+ }
267
+ if (!hasTextMarkerCharsInRange(children, brokenRefStart, segmentEnd, brokenRefStartTextOffset)) {
268
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-text-marker')
269
+ brokenRefStart = -1
270
+ brokenRefDepth = 0
271
+ brokenRefStartTextOffset = 0
272
+ continue
273
+ }
274
+ if (wrapperPrefixStats === null) {
275
+ wrapperPrefixStats = buildAsteriskWrapperPrefixStats(children)
276
+ }
277
+ if (!shouldAttemptBrokenRefRewrite(
278
+ children,
279
+ brokenRefStart,
280
+ segmentEnd,
281
+ brokenRefStartTextOffset,
282
+ wrapperPrefixStats
283
+ )) {
284
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-guard')
285
+ brokenRefStart = -1
286
+ brokenRefDepth = 0
287
+ brokenRefStartTextOffset = 0
288
+ continue
289
+ }
290
+ const fastPathResult = applyBrokenRefTokenOnlyFastPath(
291
+ children,
292
+ brokenRefStart,
293
+ segmentEnd,
294
+ linkCloseMap,
295
+ metrics,
296
+ bumpPostprocessMetric
297
+ )
298
+ if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE) {
299
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-active-signature')
300
+ brokenRefStart = -1
301
+ brokenRefDepth = 0
302
+ brokenRefStartTextOffset = 0
303
+ continue
304
+ }
305
+ if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_MATCH) {
306
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-fastpath-match')
307
+ brokenRefStart = -1
308
+ brokenRefDepth = 0
309
+ brokenRefStartTextOffset = 0
310
+ continue
311
+ }
312
+ bumpPostprocessMetric(metrics, 'brokenRefFlow', 'repaired')
313
+ return {
314
+ didRepair: true,
315
+ hasBracketText,
316
+ hasEmphasis,
317
+ hasLinkClose
318
+ }
319
+ }
320
+
321
+ return {
322
+ didRepair: false,
323
+ hasBracketText,
324
+ hasEmphasis,
325
+ hasLinkClose
326
+ }
327
+ }
328
+
329
+ const computeMaxBrokenRefRepairPass = (children, scanState) => {
330
+ resetBrokenRefScanState(scanState)
331
+ let maxRepairPass = 0
332
+ for (let j = 0; j < children.length; j++) {
333
+ const child = children[j]
334
+ if (!child || child.type !== 'text' || !child.content) continue
335
+ if (child.content.indexOf('[') === -1) continue
336
+ if (scanBrokenRefState(child.content, scanState).brokenEnd) {
337
+ maxRepairPass++
338
+ }
339
+ }
340
+ return maxRepairPass
341
+ }
342
+
343
+ const runBrokenRefRepairs = (children, maxRepairPass, scanState, metrics = null) => {
344
+ let repairPassCount = 0
345
+ let changed = false
346
+ let hasBracketText = false
347
+ let hasEmphasis = false
348
+ let hasLinkClose = false
349
+ while (repairPassCount < maxRepairPass) {
350
+ const pass = runBrokenRefRepairPass(children, scanState, metrics)
351
+ hasBracketText = pass.hasBracketText
352
+ hasEmphasis = pass.hasEmphasis
353
+ hasLinkClose = pass.hasLinkClose
354
+ if (!pass.didRepair) break
355
+ changed = true
356
+ repairPassCount++
357
+ }
358
+ return {
359
+ changed,
360
+ hasBracketText,
361
+ hasEmphasis,
362
+ hasLinkClose
363
+ }
364
+ }
365
+
366
+ const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
367
+ if (!tokens || linkCloseIdx < 0 || linkCloseIdx >= tokens.length) return null
368
+ let startIdx = -1
369
+ let foundStrongClose = -1
370
+ let foundStrongOpen = -1
371
+ for (let j = linkCloseIdx + 1; j < tokens.length; j++) {
372
+ const node = tokens[j]
373
+ if (!node) continue
374
+ if (node.type === 'strong_open') {
375
+ foundStrongOpen = j
376
+ break
377
+ }
378
+ if (node.type === 'strong_close') {
379
+ foundStrongClose = j
380
+ break
381
+ }
382
+ if (node.type === 'text' && node.content && startIdx === -1) {
383
+ startIdx = j
384
+ }
385
+ }
386
+ if (foundStrongClose === -1 || foundStrongOpen !== -1) return null
387
+ if (startIdx === -1) startIdx = foundStrongClose
388
+ return { startIdx, strongCloseIdx: foundStrongClose }
389
+ }
390
+
391
+ const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null) => {
392
+ if (!tokens || !candidate) return false
393
+ const startIdx = candidate.startIdx
394
+ const strongCloseIdx = candidate.strongCloseIdx
395
+ const endIdx = tokens.length - 1
396
+ if (isJapaneseMode && !hasJapaneseContextInRange(tokens, startIdx, endIdx)) return false
397
+ if (!hasEmphasisSignalInRange(tokens, startIdx, endIdx)) return false
398
+ if (tryFixTailPatternTokenOnly(tokens, startIdx, endIdx)) {
399
+ bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-pattern')
400
+ return true
401
+ }
402
+ if (tryFixTailDanglingStrongCloseTokenOnly(tokens, startIdx, strongCloseIdx)) {
403
+ bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-dangling-strong-close')
404
+ return true
405
+ }
406
+ return false
407
+ }
408
+
409
+ const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null) => {
410
+ let strongDepth = 0
411
+ for (let i = 0; i < tokens.length; i++) {
412
+ const t = tokens[i]
413
+ if (!t) continue
414
+ if (t.type === 'strong_open') strongDepth++
415
+ if (t.type === 'strong_close') {
416
+ if (strongDepth > 0) strongDepth--
417
+ }
418
+ if (t.type !== 'link_close') continue
419
+ if (strongDepth !== 0) continue
420
+ const candidate = scanTailRepairCandidateAfterLinkClose(tokens, i)
421
+ if (!candidate) continue
422
+ if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics)) return true
423
+ }
424
+ return false
425
+ }
426
+
427
+ const cloneMap = (map) => {
428
+ if (!map || !Array.isArray(map)) return null
429
+ return [map[0], map[1]]
430
+ }
431
+
432
+ const cloneTextToken = (source, content) => {
433
+ const token = new Token('text', '', 0)
434
+ Object.assign(token, source)
435
+ token.content = content
436
+ if (source.meta) token.meta = { ...source.meta }
437
+ return token
438
+ }
439
+
440
+ const isSoftSpaceCode = (code) => {
441
+ return code === 0x20 || code === 0x09 || code === 0x3000
442
+ }
443
+
444
+ const isAsciiWordCode = (code) => {
445
+ return (code >= 0x30 && code <= 0x39) ||
446
+ (code >= 0x41 && code <= 0x5A) ||
447
+ (code >= 0x61 && code <= 0x7A)
448
+ }
449
+
450
+ const textEndsAsciiWord = (text) => {
451
+ if (!text || text.length === 0) return false
452
+ return isAsciiWordCode(text.charCodeAt(text.length - 1))
453
+ }
454
+
455
+ const textStartsAsciiWord = (text) => {
456
+ if (!text || text.length === 0) return false
457
+ return isAsciiWordCode(text.charCodeAt(0))
458
+ }
459
+
460
+ const isEscapedMarkerAt = (content, index) => {
461
+ let slashCount = 0
462
+ for (let i = index - 1; i >= 0 && content.charCodeAt(i) === 0x5C; i--) {
463
+ slashCount++
464
+ }
465
+ return (slashCount % 2) === 1
466
+ }
467
+
468
+ const findLastStandaloneStrongMarker = (content) => {
469
+ if (!content || content.length < 2) return -1
470
+ let pos = content.lastIndexOf('**')
471
+ while (pos !== -1) {
472
+ const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
473
+ const next = pos + 2 < content.length ? content.charCodeAt(pos + 2) : 0
474
+ if (prev !== 0x2A &&
475
+ next !== 0x2A &&
476
+ !isEscapedMarkerAt(content, pos)) {
477
+ return pos
478
+ }
479
+ pos = content.lastIndexOf('**', pos - 1)
480
+ }
481
+ return -1
482
+ }
483
+
484
+ const hasLeadingStandaloneStrongMarker = (content) => {
485
+ if (!content || content.length < 2) return false
486
+ if (content.charCodeAt(0) !== 0x2A || content.charCodeAt(1) !== 0x2A) return false
487
+ if (content.length > 2 && content.charCodeAt(2) === 0x2A) return false
488
+ return true
489
+ }
490
+
491
+ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false) => {
492
+ if (!tokens || tokens.length < 3) return false
493
+ let changed = false
494
+ let linkCloseMap = null
495
+ for (let i = 1; i < tokens.length - 1; i++) {
496
+ const linkOpen = tokens[i]
497
+ if (!linkOpen || linkOpen.type !== 'link_open') continue
498
+ if (linkCloseMap === null) {
499
+ linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
500
+ }
501
+ const closeIdx = linkCloseMap.get(i) ?? -1
502
+ if (closeIdx === -1 || closeIdx + 1 >= tokens.length) continue
503
+
504
+ const left = tokens[i - 1]
505
+ const right = tokens[closeIdx + 1]
506
+ if (!left || left.type !== 'text' || !left.content) {
507
+ i = closeIdx
508
+ continue
509
+ }
510
+ if (!right || right.type !== 'text' || !right.content) {
511
+ i = closeIdx
512
+ continue
513
+ }
514
+ if (!hasLeadingStandaloneStrongMarker(right.content)) {
515
+ i = closeIdx
516
+ continue
517
+ }
518
+ const markerPos = findLastStandaloneStrongMarker(left.content)
519
+ if (markerPos === -1) {
520
+ i = closeIdx
521
+ continue
522
+ }
523
+
524
+ const prefix = left.content.slice(0, markerPos)
525
+ const leftInner = left.content.slice(markerPos + 2)
526
+ if (leftInner && isSoftSpaceCode(leftInner.charCodeAt(0))) {
527
+ i = closeIdx
528
+ continue
529
+ }
530
+ const rightTail = right.content.slice(2)
531
+ if (strictAsciiStrongGuard &&
532
+ (textEndsAsciiWord(prefix) || textStartsAsciiWord(rightTail))) {
533
+ i = closeIdx
534
+ continue
535
+ }
536
+
537
+ const replacement = []
538
+ if (prefix) replacement.push(cloneTextToken(left, prefix))
539
+
540
+ const strongOpen = new Token('strong_open', 'strong', 1)
541
+ strongOpen.markup = '**'
542
+ strongOpen.map = cloneMap(left.map) || cloneMap(linkOpen.map) || cloneMap(right.map) || null
543
+ replacement.push(strongOpen)
544
+
545
+ if (leftInner) replacement.push(cloneTextToken(left, leftInner))
546
+ for (let j = i; j <= closeIdx; j++) replacement.push(tokens[j])
547
+
548
+ const strongClose = new Token('strong_close', 'strong', -1)
549
+ strongClose.markup = '**'
550
+ strongClose.map = cloneMap(right.map) || cloneMap(linkOpen.map) || cloneMap(left.map) || null
551
+ replacement.push(strongClose)
552
+
553
+ if (rightTail) replacement.push(cloneTextToken(right, rightTail))
554
+
555
+ tokens.splice(i - 1, closeIdx - i + 3, ...replacement)
556
+ changed = true
557
+ // Token indices changed; invalidate map for the next candidate.
558
+ linkCloseMap = null
559
+ i = i - 1 + replacement.length - 1
560
+ }
561
+ return changed
562
+ }
563
+
564
+ const tryPromoteStrongAroundInlineCode = (
565
+ tokens,
566
+ strictAsciiCodeGuard = false,
567
+ strictAsciiStrongGuard = false
568
+ ) => {
569
+ if (!tokens || tokens.length < 3) return false
570
+ let changed = false
571
+ for (let i = 0; i <= tokens.length - 3; i++) {
572
+ const left = tokens[i]
573
+ const code = tokens[i + 1]
574
+ const right = tokens[i + 2]
575
+ if (!left || !code || !right) continue
576
+ if (left.type !== 'text' || !left.content) continue
577
+ if (code.type !== 'code_inline') continue
578
+ if (right.type !== 'text' || !right.content) continue
579
+ if (!hasLeadingStandaloneStrongMarker(right.content)) continue
580
+ const markerPos = findLastStandaloneStrongMarker(left.content)
581
+ if (markerPos === -1) continue
582
+
583
+ const prefix = left.content.slice(0, markerPos)
584
+ const leftInner = left.content.slice(markerPos + 2)
585
+ const rightTail = right.content.slice(2)
586
+ if (strictAsciiStrongGuard &&
587
+ (textEndsAsciiWord(prefix) || textStartsAsciiWord(rightTail))) {
588
+ continue
589
+ }
590
+ if (strictAsciiCodeGuard &&
591
+ leftInner &&
592
+ isSoftSpaceCode(leftInner.charCodeAt(0)) &&
593
+ code.content &&
594
+ isAsciiWordCode(code.content.charCodeAt(0))) {
595
+ continue
596
+ }
597
+
598
+ const replacement = []
599
+ if (prefix) replacement.push(cloneTextToken(left, prefix))
600
+
601
+ const strongOpen = new Token('strong_open', 'strong', 1)
602
+ strongOpen.markup = '**'
603
+ strongOpen.map = cloneMap(left.map) || cloneMap(code.map) || cloneMap(right.map) || null
604
+ replacement.push(strongOpen)
605
+
606
+ if (leftInner) replacement.push(cloneTextToken(left, leftInner))
607
+ replacement.push(code)
608
+
609
+ const strongClose = new Token('strong_close', 'strong', -1)
610
+ strongClose.markup = '**'
611
+ strongClose.map = cloneMap(right.map) || cloneMap(code.map) || cloneMap(left.map) || null
612
+ replacement.push(strongClose)
613
+
614
+ if (rightTail) replacement.push(cloneTextToken(right, rightTail))
615
+
616
+ tokens.splice(i, 3, ...replacement)
617
+ changed = true
618
+ i += replacement.length - 1
619
+ }
620
+ return changed
621
+ }
622
+
623
+ const processInlinePostprocessToken = (
624
+ token,
625
+ inlineContent,
626
+ state,
627
+ isJapaneseMode,
628
+ strictAsciiCodeGuard,
629
+ strictAsciiStrongGuard,
630
+ referenceCount,
631
+ metrics = null
632
+ ) => {
633
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) return
634
+ const children = token.children
635
+ const hasBracketTextInContent = inlineContent.indexOf('[') !== -1 || inlineContent.indexOf(']') !== -1
636
+ const preScan = scanInlinePostprocessSignals(children, hasBracketTextInContent)
637
+ let hasBracketText = preScan.hasBracketText
638
+ let hasEmphasis = preScan.hasEmphasis
639
+ const hasLinkOpen = preScan.hasLinkOpen
640
+ let hasLinkClose = preScan.hasLinkClose
641
+ const hasCodeInline = preScan.hasCodeInline
642
+ if (!hasEmphasis && !hasBracketText && !hasLinkOpen && !hasLinkClose && !hasCodeInline) {
643
+ return
644
+ }
645
+ if (isJapaneseMode &&
646
+ !hasJapaneseContextInRange(children, 0, children.length - 1)) {
647
+ return
648
+ }
649
+ let changed = false
650
+ if (!hasEmphasis) {
651
+ if (hasLinkOpen &&
652
+ hasLinkClose &&
653
+ tryPromoteStrongAroundInlineLink(children, strictAsciiStrongGuard)) {
654
+ hasEmphasis = true
655
+ changed = true
656
+ } else if (!hasBracketText) {
657
+ if (!hasLinkOpen &&
658
+ !hasLinkClose &&
659
+ tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard)) {
660
+ hasEmphasis = true
661
+ changed = true
662
+ } else {
663
+ return
664
+ }
665
+ }
666
+ }
667
+ let shouldTryBrokenRefRepair = hasLinkOpen && hasLinkClose && hasBracketText && referenceCount > 0
668
+ if (shouldTryBrokenRefRepair && inlineContent.indexOf('***') !== -1) {
669
+ shouldTryBrokenRefRepair = false
670
+ }
671
+ if (shouldTryBrokenRefRepair) {
672
+ const scanState = { depth: 0, brokenEnd: false, tailOpen: -1 }
673
+ const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
674
+ if (maxRepairPass > 0) {
675
+ const repairs = runBrokenRefRepairs(children, maxRepairPass, scanState, metrics)
676
+ hasBracketText = repairs.hasBracketText
677
+ hasEmphasis = repairs.hasEmphasis
678
+ hasLinkClose = repairs.hasLinkClose
679
+ if (repairs.changed) changed = true
680
+ }
681
+ }
682
+ if (hasEmphasis) {
683
+ if (fixEmOuterStrongSequence(children)) changed = true
684
+ if (hasLinkClose && fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics)) changed = true
685
+ if (hasLinkClose && fixLeadingAsteriskEm(children)) changed = true
686
+ if (fixTrailingStrong(children)) changed = true
687
+ if (sanitizeEmStrongBalance(children)) changed = true
688
+ }
689
+ if (changed) rebuildInlineLevels(children)
690
+ if (!hasBracketText) return
691
+ if (referenceCount > 0) convertCollapsedReferenceLinks(children, state)
692
+ if (referenceCount === 0 && !hasLinkClose) return
693
+ mergeBrokenMarksAroundLinks(children)
694
+ }
695
+
696
+ const registerTokenPostprocess = (md, baseOpt) => {
697
+ if (md.__strongJaTokenPostprocessRegistered) return
698
+ md.__strongJaTokenPostprocessRegistered = true
699
+ md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
700
+ if (!state || !state.tokens) return
701
+ const opt = getRuntimeOpt(state, baseOpt)
702
+ const modeFlags = opt.__strongJaModeFlags
703
+ const isJapaneseMode = (modeFlags & MODE_FLAG_JAPANESE_ANY) !== 0
704
+ const strictAsciiCodeGuard = (modeFlags & MODE_FLAG_JAPANESE_PLUS) !== 0
705
+ const strictAsciiStrongGuard = (modeFlags & MODE_FLAG_AGGRESSIVE) === 0
706
+ if (modeFlags & MODE_FLAG_COMPATIBLE) return
707
+ if (opt.postprocess === false) return
708
+ const references = state.env && state.env.references ? state.env.references : null
709
+ if (state.__strongJaReferenceCount === undefined) {
710
+ state.__strongJaReferenceCount = references ? Object.keys(references).length : 0
711
+ }
712
+ const referenceCount = state.__strongJaReferenceCount
713
+ const metrics = getPostprocessMetrics(state)
714
+ for (let i = 0; i < state.tokens.length; i++) {
715
+ const token = state.tokens[i]
716
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
717
+ const inlineContent = typeof token.content === 'string' ? token.content : ''
718
+ if (!hasMarkerChars(inlineContent)) continue
719
+ processInlinePostprocessToken(
720
+ token,
721
+ inlineContent,
722
+ state,
723
+ isJapaneseMode,
724
+ strictAsciiCodeGuard,
725
+ strictAsciiStrongGuard,
726
+ referenceCount,
727
+ metrics
728
+ )
729
+ }
730
+ })
731
+ }
732
+
733
+ export { registerTokenPostprocess }