@peaceroad/markdown-it-strong-ja 0.8.0 → 0.9.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.
@@ -1,139 +1,30 @@
1
1
  import Token from 'markdown-it/lib/token.mjs'
2
2
  import { buildLinkCloseMap, convertCollapsedReferenceLinks, mergeBrokenMarksAroundLinks } from '../token-link-utils.js'
3
+ import { computeMaxBrokenRefRepairPass, runBrokenRefRepairs } from './broken-ref.js'
3
4
  import {
4
5
  rebuildInlineLevels,
6
+ rebuildInlineLevelsFrom,
5
7
  fixEmOuterStrongSequence,
6
8
  fixLeadingAsteriskEm,
7
9
  fixTrailingStrong
8
10
  } from '../token-core.js'
9
11
  import {
10
- getInlineWrapperBase,
11
12
  getRuntimeOpt,
12
- MODE_FLAG_COMPATIBLE,
13
- MODE_FLAG_AGGRESSIVE,
14
- MODE_FLAG_JAPANESE_PLUS,
15
- MODE_FLAG_JAPANESE_ANY
13
+ hasRuntimeOverride,
14
+ getReferenceCount
16
15
  } from '../token-utils.js'
17
16
  import {
18
17
  hasMarkerChars,
19
- isAsteriskEmphasisToken,
20
18
  hasJapaneseContextInRange,
21
19
  hasEmphasisSignalInRange,
22
- hasTextMarkerCharsInRange,
23
20
  buildAsteriskWrapperPrefixStats,
24
- shouldAttemptBrokenRefRewrite,
25
21
  scanInlinePostprocessSignals
26
22
  } from './guards.js'
27
23
  import {
28
- BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE,
29
- BROKEN_REF_FAST_PATH_RESULT_NO_MATCH,
30
- applyBrokenRefTokenOnlyFastPath,
31
24
  tryFixTailPatternTokenOnly,
32
25
  tryFixTailDanglingStrongCloseTokenOnly
33
26
  } from './fastpaths.js'
34
27
 
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
28
  const fallbackMarkupByType = (type) => {
138
29
  if (type === 'strong_open' || type === 'strong_close') return '**'
139
30
  if (type === 'em_open' || type === 'em_close') return '*'
@@ -151,7 +42,7 @@ const makeTokenLiteralText = (token) => {
151
42
  token.info = ''
152
43
  }
153
44
 
154
- const sanitizeEmStrongBalance = (tokens) => {
45
+ const sanitizeEmStrongBalance = (tokens, onChangeStart = null) => {
155
46
  if (!tokens || tokens.length === 0) return false
156
47
  const stack = []
157
48
  let changed = false
@@ -168,6 +59,7 @@ const sanitizeEmStrongBalance = (tokens) => {
168
59
  stack.pop()
169
60
  continue
170
61
  }
62
+ if (onChangeStart) onChangeStart(i)
171
63
  makeTokenLiteralText(token)
172
64
  changed = true
173
65
  }
@@ -175,6 +67,7 @@ const sanitizeEmStrongBalance = (tokens) => {
175
67
  const entry = stack[i]
176
68
  const token = tokens[entry.idx]
177
69
  if (!token) continue
70
+ if (onChangeStart) onChangeStart(entry.idx)
178
71
  makeTokenLiteralText(token)
179
72
  changed = true
180
73
  }
@@ -188,179 +81,92 @@ const getPostprocessMetrics = (state) => {
188
81
  return metrics
189
82
  }
190
83
 
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
84
+ const buildInlinePostprocessFacts = (children, inlineContent) => {
85
+ const preScan = scanInlinePostprocessSignals(children)
86
+ return {
87
+ hasBracketText: inlineContent.indexOf('[') !== -1 || inlineContent.indexOf(']') !== -1,
88
+ hasEmphasis: preScan.hasEmphasis,
89
+ hasLinkOpen: preScan.hasLinkOpen,
90
+ hasLinkClose: preScan.hasLinkClose,
91
+ hasCodeInline: preScan.hasCodeInline,
92
+ linkCloseMap: undefined,
93
+ wrapperPrefixStats: undefined,
94
+ rebuildLevelStart: undefined
197
95
  }
198
- table[key] = (table[key] || 0) + 1
199
96
  }
200
97
 
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
- }
98
+ const ensureInlineLinkCloseMap = (facts, tokens) => {
99
+ if (!tokens || tokens.length === 0) return new Map()
100
+ if (!facts) return buildLinkCloseMap(tokens, 0, tokens.length - 1)
101
+ if (facts.linkCloseMap === undefined) {
102
+ facts.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
103
+ }
104
+ return facts.linkCloseMap
105
+ }
242
106
 
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
- }
107
+ const ensureInlineWrapperPrefixStats = (facts, tokens) => {
108
+ if (!tokens || tokens.length === 0) return null
109
+ if (!facts) return buildAsteriskWrapperPrefixStats(tokens)
110
+ if (facts.wrapperPrefixStats === undefined) {
111
+ facts.wrapperPrefixStats = buildAsteriskWrapperPrefixStats(tokens)
319
112
  }
113
+ return facts.wrapperPrefixStats
114
+ }
320
115
 
321
- return {
322
- didRepair: false,
323
- hasBracketText,
324
- hasEmphasis,
325
- hasLinkClose
116
+ const invalidateInlineDerivedCaches = (facts) => {
117
+ if (!facts) return
118
+ facts.linkCloseMap = undefined
119
+ facts.wrapperPrefixStats = undefined
120
+ }
121
+
122
+ const markInlineLevelRebuildFrom = (facts, startIdx) => {
123
+ if (!facts) return
124
+ const from = startIdx > 0 ? startIdx : 0
125
+ if (facts.rebuildLevelStart === undefined || from < facts.rebuildLevelStart) {
126
+ facts.rebuildLevelStart = from
326
127
  }
327
128
  }
328
129
 
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
- }
130
+ const rebuildInlineLevelsForFacts = (tokens, facts) => {
131
+ if (!facts || facts.rebuildLevelStart === undefined) {
132
+ rebuildInlineLevels(tokens)
133
+ } else {
134
+ rebuildInlineLevelsFrom(tokens, facts.rebuildLevelStart)
135
+ }
136
+ if (facts) {
137
+ facts.rebuildLevelStart = undefined
339
138
  }
340
- return maxRepairPass
341
139
  }
342
140
 
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++
141
+ const createInlineChangeMarker = (facts) => {
142
+ return (startIdx) => {
143
+ markInlineLevelRebuildFrom(facts, startIdx)
357
144
  }
358
- return {
359
- changed,
360
- hasBracketText,
361
- hasEmphasis,
362
- hasLinkClose
145
+ }
146
+
147
+ const finalizeInlineLinkRepairStage = (children, facts, markChangedFrom) => {
148
+ invalidateInlineDerivedCaches(facts)
149
+ if (!mergeBrokenMarksAroundLinks(children, markChangedFrom)) return false
150
+ invalidateInlineDerivedCaches(facts)
151
+ rebuildInlineLevelsForFacts(children, facts)
152
+ return true
153
+ }
154
+
155
+ const BROKEN_REF_REPAIR_HOOKS = {
156
+ ensureLinkCloseMap: ensureInlineLinkCloseMap,
157
+ ensureWrapperPrefixStats: ensureInlineWrapperPrefixStats,
158
+ invalidateDerivedCaches: invalidateInlineDerivedCaches,
159
+ markLevelRebuildFrom: markInlineLevelRebuildFrom
160
+ }
161
+
162
+ const bumpPostprocessMetric = (metrics, bucket, key) => {
163
+ if (!metrics || !bucket || !key) return
164
+ let table = metrics[bucket]
165
+ if (!table || typeof table !== 'object') {
166
+ table = Object.create(null)
167
+ metrics[bucket] = table
363
168
  }
169
+ table[key] = (table[key] || 0) + 1
364
170
  }
365
171
 
366
172
  const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
@@ -388,7 +194,7 @@ const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
388
194
  return { startIdx, strongCloseIdx: foundStrongClose }
389
195
  }
390
196
 
391
- const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null) => {
197
+ const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null, onChangeStart = null) => {
392
198
  if (!tokens || !candidate) return false
393
199
  const startIdx = candidate.startIdx
394
200
  const strongCloseIdx = candidate.strongCloseIdx
@@ -396,17 +202,19 @@ const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = nul
396
202
  if (isJapaneseMode && !hasJapaneseContextInRange(tokens, startIdx, endIdx)) return false
397
203
  if (!hasEmphasisSignalInRange(tokens, startIdx, endIdx)) return false
398
204
  if (tryFixTailPatternTokenOnly(tokens, startIdx, endIdx)) {
205
+ if (onChangeStart) onChangeStart(startIdx)
399
206
  bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-pattern')
400
207
  return true
401
208
  }
402
209
  if (tryFixTailDanglingStrongCloseTokenOnly(tokens, startIdx, strongCloseIdx)) {
210
+ if (onChangeStart) onChangeStart(startIdx)
403
211
  bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-dangling-strong-close')
404
212
  return true
405
213
  }
406
214
  return false
407
215
  }
408
216
 
409
- const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null) => {
217
+ const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null, onChangeStart = null) => {
410
218
  let strongDepth = 0
411
219
  for (let i = 0; i < tokens.length; i++) {
412
220
  const t = tokens[i]
@@ -419,7 +227,7 @@ const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null) =>
419
227
  if (strongDepth !== 0) continue
420
228
  const candidate = scanTailRepairCandidateAfterLinkClose(tokens, i)
421
229
  if (!candidate) continue
422
- if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics)) return true
230
+ if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics, onChangeStart)) return true
423
231
  }
424
232
  return false
425
233
  }
@@ -441,6 +249,9 @@ const isSoftSpaceCode = (code) => {
441
249
  return code === 0x20 || code === 0x09 || code === 0x3000
442
250
  }
443
251
 
252
+ const CHAR_ASTERISK = 0x2A // *
253
+ const CHAR_BACKSLASH = 0x5C // \
254
+
444
255
  const isAsciiWordCode = (code) => {
445
256
  return (code >= 0x30 && code <= 0x39) ||
446
257
  (code >= 0x41 && code <= 0x5A) ||
@@ -459,7 +270,7 @@ const textStartsAsciiWord = (text) => {
459
270
 
460
271
  const isEscapedMarkerAt = (content, index) => {
461
272
  let slashCount = 0
462
- for (let i = index - 1; i >= 0 && content.charCodeAt(i) === 0x5C; i--) {
273
+ for (let i = index - 1; i >= 0 && content.charCodeAt(i) === CHAR_BACKSLASH; i--) {
463
274
  slashCount++
464
275
  }
465
276
  return (slashCount % 2) === 1
@@ -467,37 +278,34 @@ const isEscapedMarkerAt = (content, index) => {
467
278
 
468
279
  const findLastStandaloneStrongMarker = (content) => {
469
280
  if (!content || content.length < 2) return -1
470
- let pos = content.lastIndexOf('**')
471
- while (pos !== -1) {
281
+ for (let pos = content.length - 2; pos >= 0; pos--) {
282
+ if (content.charCodeAt(pos) !== CHAR_ASTERISK ||
283
+ content.charCodeAt(pos + 1) !== CHAR_ASTERISK) {
284
+ continue
285
+ }
472
286
  const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
473
287
  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)
288
+ if (prev === CHAR_ASTERISK || next === CHAR_ASTERISK) continue
289
+ if (prev === CHAR_BACKSLASH && isEscapedMarkerAt(content, pos)) continue
290
+ return pos
480
291
  }
481
292
  return -1
482
293
  }
483
294
 
484
295
  const hasLeadingStandaloneStrongMarker = (content) => {
485
296
  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
297
+ if (content.charCodeAt(0) !== CHAR_ASTERISK || content.charCodeAt(1) !== CHAR_ASTERISK) return false
298
+ if (content.length > 2 && content.charCodeAt(2) === CHAR_ASTERISK) return false
488
299
  return true
489
300
  }
490
301
 
491
- const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false) => {
302
+ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false, facts = null) => {
492
303
  if (!tokens || tokens.length < 3) return false
493
304
  let changed = false
494
- let linkCloseMap = null
495
305
  for (let i = 1; i < tokens.length - 1; i++) {
496
306
  const linkOpen = tokens[i]
497
307
  if (!linkOpen || linkOpen.type !== 'link_open') continue
498
- if (linkCloseMap === null) {
499
- linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
500
- }
308
+ const linkCloseMap = ensureInlineLinkCloseMap(facts, tokens)
501
309
  const closeIdx = linkCloseMap.get(i) ?? -1
502
310
  if (closeIdx === -1 || closeIdx + 1 >= tokens.length) continue
503
311
 
@@ -554,8 +362,8 @@ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false
554
362
 
555
363
  tokens.splice(i - 1, closeIdx - i + 3, ...replacement)
556
364
  changed = true
557
- // Token indices changed; invalidate map for the next candidate.
558
- linkCloseMap = null
365
+ invalidateInlineDerivedCaches(facts)
366
+ markInlineLevelRebuildFrom(facts, i - 1)
559
367
  i = i - 1 + replacement.length - 1
560
368
  }
561
369
  return changed
@@ -564,7 +372,8 @@ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false
564
372
  const tryPromoteStrongAroundInlineCode = (
565
373
  tokens,
566
374
  strictAsciiCodeGuard = false,
567
- strictAsciiStrongGuard = false
375
+ strictAsciiStrongGuard = false,
376
+ facts = null
568
377
  ) => {
569
378
  if (!tokens || tokens.length < 3) return false
570
379
  let changed = false
@@ -615,82 +424,200 @@ const tryPromoteStrongAroundInlineCode = (
615
424
 
616
425
  tokens.splice(i, 3, ...replacement)
617
426
  changed = true
427
+ invalidateInlineDerivedCaches(facts)
428
+ markInlineLevelRebuildFrom(facts, i)
618
429
  i += replacement.length - 1
619
430
  }
620
431
  return changed
621
432
  }
622
433
 
434
+ const tryActivateInlineEmphasis = (
435
+ children,
436
+ facts,
437
+ strictAsciiCodeGuard,
438
+ strictAsciiStrongGuard
439
+ ) => {
440
+ if (!facts || facts.hasEmphasis) return false
441
+ if (facts.hasLinkOpen &&
442
+ facts.hasLinkClose &&
443
+ tryPromoteStrongAroundInlineLink(children, strictAsciiStrongGuard, facts)) {
444
+ facts.hasEmphasis = true
445
+ return true
446
+ }
447
+ if (facts.hasBracketText || facts.hasLinkOpen || facts.hasLinkClose) return false
448
+ if (!facts.hasCodeInline) return false
449
+ if (tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard, facts)) {
450
+ facts.hasEmphasis = true
451
+ return true
452
+ }
453
+ return false
454
+ }
455
+
456
+ const shouldRunInlineBrokenRefRepair = (facts, inlineContent, state) => {
457
+ if (!facts || !facts.hasLinkOpen || !facts.hasLinkClose || !facts.hasBracketText) return false
458
+ if (inlineContent.indexOf('***') !== -1) return false
459
+ return getReferenceCount(state) > 0
460
+ }
461
+
462
+ const applyBrokenRefRepairFacts = (facts, repairs) => {
463
+ if (!facts || !repairs) return
464
+ facts.hasBracketText = repairs.hasBracketText
465
+ facts.hasEmphasis = repairs.hasEmphasis
466
+ facts.hasLinkClose = repairs.hasLinkClose
467
+ }
468
+
469
+ const createBrokenRefScanState = () => {
470
+ return { depth: 0, brokenEnd: false, tailOpen: -1 }
471
+ }
472
+
473
+ const runInlineBrokenRefRepairStage = (children, facts, inlineContent, state) => {
474
+ if (!shouldRunInlineBrokenRefRepair(facts, inlineContent, state)) return false
475
+ const scanState = createBrokenRefScanState()
476
+ const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
477
+ if (maxRepairPass <= 0) return false
478
+ const repairs = runBrokenRefRepairs(
479
+ children,
480
+ maxRepairPass,
481
+ scanState,
482
+ getPostprocessMetrics(state),
483
+ facts,
484
+ BROKEN_REF_REPAIR_HOOKS
485
+ )
486
+ applyBrokenRefRepairFacts(facts, repairs)
487
+ return repairs.changed
488
+ }
489
+
490
+ const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) => {
491
+ if (!facts.hasEmphasis) return false
492
+ let changed = false
493
+ const markChangedFrom = createInlineChangeMarker(facts)
494
+ if (fixEmOuterStrongSequence(children, markChangedFrom)) changed = true
495
+ if (facts.hasLinkClose) {
496
+ const metrics = getPostprocessMetrics(state)
497
+ if (fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) changed = true
498
+ if (fixLeadingAsteriskEm(children, markChangedFrom)) changed = true
499
+ }
500
+ if (fixTrailingStrong(children, markChangedFrom)) changed = true
501
+ if (sanitizeEmStrongBalance(children, markChangedFrom)) changed = true
502
+ return changed
503
+ }
504
+
505
+ const shouldRunInlineCollapsedRefRepair = (facts, state) => {
506
+ if (!facts || !facts.hasBracketText) return false
507
+ return getReferenceCount(state) > 0
508
+ }
509
+
510
+ const applyCollapsedRefRepairFacts = (facts) => {
511
+ if (!facts) return
512
+ facts.hasLinkOpen = true
513
+ facts.hasLinkClose = true
514
+ }
515
+
516
+ const rewriteInlineCollapsedReferences = (children, facts, state, markChangedFrom) => {
517
+ const changed = convertCollapsedReferenceLinks(
518
+ children,
519
+ state,
520
+ facts,
521
+ markChangedFrom
522
+ )
523
+ if (!changed) return false
524
+ applyCollapsedRefRepairFacts(facts)
525
+ return true
526
+ }
527
+
528
+ const runInlineCollapsedRefStage = (children, facts, state) => {
529
+ if (!shouldRunInlineCollapsedRefRepair(facts, state)) return false
530
+ const markChangedFrom = createInlineChangeMarker(facts)
531
+ if (!rewriteInlineCollapsedReferences(children, facts, state, markChangedFrom)) return false
532
+ finalizeInlineLinkRepairStage(children, facts, markChangedFrom)
533
+ return true
534
+ }
535
+
536
+ const shouldSkipInlinePostprocessToken = (children, facts, isJapaneseMode) => {
537
+ if (!facts.hasEmphasis &&
538
+ !facts.hasBracketText &&
539
+ !facts.hasLinkOpen &&
540
+ !facts.hasLinkClose &&
541
+ !facts.hasCodeInline) {
542
+ return true
543
+ }
544
+ if (isJapaneseMode &&
545
+ !hasJapaneseContextInRange(children, 0, children.length - 1)) {
546
+ return true
547
+ }
548
+ return false
549
+ }
550
+
551
+ const runInlineCoreRepairStages = (
552
+ children,
553
+ facts,
554
+ inlineContent,
555
+ state,
556
+ isJapaneseMode,
557
+ strictAsciiCodeGuard,
558
+ strictAsciiStrongGuard
559
+ ) => {
560
+ let changed = false
561
+ if (!facts.hasEmphasis && tryActivateInlineEmphasis(
562
+ children,
563
+ facts,
564
+ strictAsciiCodeGuard,
565
+ strictAsciiStrongGuard
566
+ )) {
567
+ changed = true
568
+ } else if (!facts.hasEmphasis && !facts.hasBracketText) {
569
+ return false
570
+ }
571
+ if (runInlineBrokenRefRepairStage(children, facts, inlineContent, state)) changed = true
572
+ if (runInlineEmphasisRepairStage(children, facts, state, isJapaneseMode)) changed = true
573
+ return changed
574
+ }
575
+
623
576
  const processInlinePostprocessToken = (
624
577
  token,
625
578
  inlineContent,
626
579
  state,
627
580
  isJapaneseMode,
628
581
  strictAsciiCodeGuard,
629
- strictAsciiStrongGuard,
630
- referenceCount,
631
- metrics = null
582
+ strictAsciiStrongGuard
632
583
  ) => {
633
584
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) return
634
585
  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
586
+ const facts = buildInlinePostprocessFacts(children, inlineContent)
587
+ if (shouldSkipInlinePostprocessToken(children, facts, isJapaneseMode)) return
588
+ const changed = runInlineCoreRepairStages(
589
+ children,
590
+ facts,
591
+ inlineContent,
592
+ state,
593
+ isJapaneseMode,
594
+ strictAsciiCodeGuard,
595
+ strictAsciiStrongGuard
596
+ )
597
+ if (changed) rebuildInlineLevelsForFacts(children, facts)
598
+ runInlineCollapsedRefStage(children, facts, state)
599
+ }
600
+
601
+ const processInlinePostprocessStateTokens = (
602
+ state,
603
+ isJapaneseMode,
604
+ strictAsciiCodeGuard,
605
+ strictAsciiStrongGuard
606
+ ) => {
607
+ for (let i = 0; i < state.tokens.length; i++) {
608
+ const token = state.tokens[i]
609
+ if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
610
+ const inlineContent = typeof token.content === 'string' ? token.content : ''
611
+ if (!hasMarkerChars(inlineContent)) continue
612
+ processInlinePostprocessToken(
613
+ token,
614
+ inlineContent,
615
+ state,
616
+ isJapaneseMode,
617
+ strictAsciiCodeGuard,
618
+ strictAsciiStrongGuard
619
+ )
688
620
  }
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
621
  }
695
622
 
696
623
  const registerTokenPostprocess = (md, baseOpt) => {
@@ -698,35 +625,18 @@ const registerTokenPostprocess = (md, baseOpt) => {
698
625
  md.__strongJaTokenPostprocessRegistered = true
699
626
  md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
700
627
  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
- }
628
+ const overrideOpt = state.env && state.env.__strongJaTokenOpt
629
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(state, baseOpt) : baseOpt
630
+ if (!opt.__strongJaPostprocessActive) return
631
+ const isJapaneseMode = opt.__strongJaIsJapaneseMode
632
+ const strictAsciiCodeGuard = opt.__strongJaStrictAsciiCodeGuard
633
+ const strictAsciiStrongGuard = opt.__strongJaStrictAsciiStrongGuard
634
+ processInlinePostprocessStateTokens(
635
+ state,
636
+ isJapaneseMode,
637
+ strictAsciiCodeGuard,
638
+ strictAsciiStrongGuard
639
+ )
730
640
  })
731
641
  }
732
642