@peaceroad/markdown-it-strong-ja 0.8.1 → 0.9.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.
@@ -1,6 +1,6 @@
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
+ import { runBrokenRefRepairs } from './broken-ref.js'
4
4
  import {
5
5
  rebuildInlineLevels,
6
6
  rebuildInlineLevelsFrom,
@@ -10,68 +10,36 @@ import {
10
10
  } from '../token-core.js'
11
11
  import {
12
12
  getRuntimeOpt,
13
- getReferenceCount
13
+ hasRuntimeOverride,
14
+ getReferenceCount,
15
+ isAsciiWordCode,
16
+ isSoftSpaceCode,
17
+ cloneMap
14
18
  } from '../token-utils.js'
15
19
  import {
16
20
  hasMarkerChars,
17
21
  hasJapaneseContextInRange,
18
22
  hasEmphasisSignalInRange,
19
23
  buildAsteriskWrapperPrefixStats,
20
- scanInlinePostprocessSignals
24
+ scanInlinePostprocessSignals,
25
+ INLINE_REPAIR_EM_OUTER_STRONG_SEQUENCE,
26
+ INLINE_REPAIR_TAIL_AFTER_LINK,
27
+ INLINE_REPAIR_LEADING_ASTERISK_EM,
28
+ INLINE_REPAIR_TRAILING_STRONG,
29
+ INLINE_REPAIR_BALANCE_SANITIZE
21
30
  } from './guards.js'
22
31
  import {
23
32
  tryFixTailPatternTokenOnly,
24
33
  tryFixTailDanglingStrongCloseTokenOnly
25
34
  } from './fastpaths.js'
35
+ import { sanitizeEmStrongBalance } from './emphasis-balance.js'
26
36
 
27
- const fallbackMarkupByType = (type) => {
28
- if (type === 'strong_open' || type === 'strong_close') return '**'
29
- if (type === 'em_open' || type === 'em_close') return '*'
30
- return ''
31
- }
32
-
33
- const makeTokenLiteralText = (token) => {
34
- if (!token) return
35
- const literal = token.markup || fallbackMarkupByType(token.type)
36
- token.type = 'text'
37
- token.tag = ''
38
- token.nesting = 0
39
- token.content = literal
40
- token.markup = ''
41
- token.info = ''
42
- }
43
-
44
- const sanitizeEmStrongBalance = (tokens, onChangeStart = null) => {
45
- if (!tokens || tokens.length === 0) return false
46
- const stack = []
47
- let changed = false
48
- for (let i = 0; i < tokens.length; i++) {
49
- const token = tokens[i]
50
- if (!token || !token.type) continue
51
- if (token.type === 'strong_open' || token.type === 'em_open') {
52
- stack.push({ type: token.type, idx: i })
53
- continue
54
- }
55
- if (token.type !== 'strong_close' && token.type !== 'em_close') continue
56
- const expected = token.type === 'strong_close' ? 'strong_open' : 'em_open'
57
- if (stack.length > 0 && stack[stack.length - 1].type === expected) {
58
- stack.pop()
59
- continue
60
- }
61
- if (onChangeStart) onChangeStart(i)
62
- makeTokenLiteralText(token)
63
- changed = true
64
- }
65
- for (let i = stack.length - 1; i >= 0; i--) {
66
- const entry = stack[i]
67
- const token = tokens[entry.idx]
68
- if (!token) continue
69
- if (onChangeStart) onChangeStart(entry.idx)
70
- makeTokenLiteralText(token)
71
- changed = true
72
- }
73
- return changed
74
- }
37
+ const INLINE_REPAIR_ALL_EMPHASIS_FIXERS =
38
+ INLINE_REPAIR_EM_OUTER_STRONG_SEQUENCE |
39
+ INLINE_REPAIR_TAIL_AFTER_LINK |
40
+ INLINE_REPAIR_LEADING_ASTERISK_EM |
41
+ INLINE_REPAIR_TRAILING_STRONG |
42
+ INLINE_REPAIR_BALANCE_SANITIZE
75
43
 
76
44
  const getPostprocessMetrics = (state) => {
77
45
  if (!state || !state.env) return null
@@ -80,53 +48,23 @@ const getPostprocessMetrics = (state) => {
80
48
  return metrics
81
49
  }
82
50
 
83
- const buildInlinePostprocessFacts = (children, inlineContent) => {
84
- const preScan = scanInlinePostprocessSignals(children)
51
+ const buildInlinePostprocessFacts = (children, inlineContent, collectJapaneseContext) => {
52
+ const preScan = scanInlinePostprocessSignals(children, collectJapaneseContext)
85
53
  return {
86
54
  hasBracketText: inlineContent.indexOf('[') !== -1 || inlineContent.indexOf(']') !== -1,
87
55
  hasEmphasis: preScan.hasEmphasis,
56
+ hasAsteriskWrapperImbalance: preScan.hasAsteriskWrapperImbalance,
88
57
  hasLinkOpen: preScan.hasLinkOpen,
89
58
  hasLinkClose: preScan.hasLinkClose,
90
- hasCodeInline: undefined,
91
- referenceCount: undefined,
92
- metrics: undefined,
59
+ hasCodeInline: preScan.hasCodeInline,
60
+ hasJapaneseContext: preScan.hasJapaneseContext,
61
+ repairMask: preScan.repairMask,
93
62
  linkCloseMap: undefined,
94
63
  wrapperPrefixStats: undefined,
95
64
  rebuildLevelStart: undefined
96
65
  }
97
66
  }
98
67
 
99
- const ensureInlineHasCodeInline = (facts, tokens) => {
100
- if (facts.hasCodeInline !== undefined) return facts.hasCodeInline
101
- let hasCodeInline = false
102
- if (tokens && tokens.length > 0) {
103
- for (let idx = 0; idx < tokens.length; idx++) {
104
- if (tokens[idx] && tokens[idx].type === 'code_inline') {
105
- hasCodeInline = true
106
- break
107
- }
108
- }
109
- }
110
- facts.hasCodeInline = hasCodeInline
111
- return hasCodeInline
112
- }
113
-
114
- const ensureInlineReferenceCount = (facts, state) => {
115
- if (!facts || !facts.hasBracketText) return 0
116
- if (facts.referenceCount === undefined) {
117
- facts.referenceCount = getReferenceCount(state)
118
- }
119
- return facts.referenceCount
120
- }
121
-
122
- const ensureInlineMetrics = (facts, state) => {
123
- if (!facts) return null
124
- if (facts.metrics === undefined) {
125
- facts.metrics = getPostprocessMetrics(state)
126
- }
127
- return facts.metrics
128
- }
129
-
130
68
  const ensureInlineLinkCloseMap = (facts, tokens) => {
131
69
  if (!tokens || tokens.length === 0) return new Map()
132
70
  if (!facts) return buildLinkCloseMap(tokens, 0, tokens.length - 1)
@@ -191,14 +129,14 @@ const BROKEN_REF_REPAIR_HOOKS = {
191
129
  markLevelRebuildFrom: markInlineLevelRebuildFrom
192
130
  }
193
131
 
194
- const bumpPostprocessMetric = (metrics, bucket, key) => {
195
- if (!metrics || !bucket || !key) return
132
+ const bumpPostprocessMetric = (metrics, bucket, key, delta = 1) => {
133
+ if (!metrics || !bucket || !key || delta <= 0) return
196
134
  let table = metrics[bucket]
197
135
  if (!table || typeof table !== 'object') {
198
136
  table = Object.create(null)
199
137
  metrics[bucket] = table
200
138
  }
201
- table[key] = (table[key] || 0) + 1
139
+ table[key] = (table[key] || 0) + delta
202
140
  }
203
141
 
204
142
  const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
@@ -264,11 +202,6 @@ const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null, onC
264
202
  return false
265
203
  }
266
204
 
267
- const cloneMap = (map) => {
268
- if (!map || !Array.isArray(map)) return null
269
- return [map[0], map[1]]
270
- }
271
-
272
205
  const cloneTextToken = (source, content) => {
273
206
  const token = new Token('text', '', 0)
274
207
  Object.assign(token, source)
@@ -277,15 +210,8 @@ const cloneTextToken = (source, content) => {
277
210
  return token
278
211
  }
279
212
 
280
- const isSoftSpaceCode = (code) => {
281
- return code === 0x20 || code === 0x09 || code === 0x3000
282
- }
283
-
284
- const isAsciiWordCode = (code) => {
285
- return (code >= 0x30 && code <= 0x39) ||
286
- (code >= 0x41 && code <= 0x5A) ||
287
- (code >= 0x61 && code <= 0x7A)
288
- }
213
+ const CHAR_ASTERISK = 0x2A // *
214
+ const CHAR_BACKSLASH = 0x5C // \
289
215
 
290
216
  const textEndsAsciiWord = (text) => {
291
217
  if (!text || text.length === 0) return false
@@ -299,7 +225,7 @@ const textStartsAsciiWord = (text) => {
299
225
 
300
226
  const isEscapedMarkerAt = (content, index) => {
301
227
  let slashCount = 0
302
- for (let i = index - 1; i >= 0 && content.charCodeAt(i) === 0x5C; i--) {
228
+ for (let i = index - 1; i >= 0 && content.charCodeAt(i) === CHAR_BACKSLASH; i--) {
303
229
  slashCount++
304
230
  }
305
231
  return (slashCount % 2) === 1
@@ -307,24 +233,24 @@ const isEscapedMarkerAt = (content, index) => {
307
233
 
308
234
  const findLastStandaloneStrongMarker = (content) => {
309
235
  if (!content || content.length < 2) return -1
310
- let pos = content.lastIndexOf('**')
311
- while (pos !== -1) {
236
+ for (let pos = content.length - 2; pos >= 0; pos--) {
237
+ if (content.charCodeAt(pos) !== CHAR_ASTERISK ||
238
+ content.charCodeAt(pos + 1) !== CHAR_ASTERISK) {
239
+ continue
240
+ }
312
241
  const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
313
242
  const next = pos + 2 < content.length ? content.charCodeAt(pos + 2) : 0
314
- if (prev !== 0x2A &&
315
- next !== 0x2A &&
316
- !isEscapedMarkerAt(content, pos)) {
317
- return pos
318
- }
319
- pos = content.lastIndexOf('**', pos - 1)
243
+ if (prev === CHAR_ASTERISK || next === CHAR_ASTERISK) continue
244
+ if (prev === CHAR_BACKSLASH && isEscapedMarkerAt(content, pos)) continue
245
+ return pos
320
246
  }
321
247
  return -1
322
248
  }
323
249
 
324
250
  const hasLeadingStandaloneStrongMarker = (content) => {
325
251
  if (!content || content.length < 2) return false
326
- if (content.charCodeAt(0) !== 0x2A || content.charCodeAt(1) !== 0x2A) return false
327
- if (content.length > 2 && content.charCodeAt(2) === 0x2A) return false
252
+ if (content.charCodeAt(0) !== CHAR_ASTERISK || content.charCodeAt(1) !== CHAR_ASTERISK) return false
253
+ if (content.length > 2 && content.charCodeAt(2) === CHAR_ASTERISK) return false
328
254
  return true
329
255
  }
330
256
 
@@ -474,7 +400,7 @@ const tryActivateInlineEmphasis = (
474
400
  return true
475
401
  }
476
402
  if (facts.hasBracketText || facts.hasLinkOpen || facts.hasLinkClose) return false
477
- if (!ensureInlineHasCodeInline(facts, children)) return false
403
+ if (!facts.hasCodeInline) return false
478
404
  if (tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard, facts)) {
479
405
  facts.hasEmphasis = true
480
406
  return true
@@ -485,93 +411,108 @@ const tryActivateInlineEmphasis = (
485
411
  const shouldRunInlineBrokenRefRepair = (facts, inlineContent, state) => {
486
412
  if (!facts || !facts.hasLinkOpen || !facts.hasLinkClose || !facts.hasBracketText) return false
487
413
  if (inlineContent.indexOf('***') !== -1) return false
488
- return ensureInlineReferenceCount(facts, state) > 0
489
- }
490
-
491
- const applyBrokenRefRepairFacts = (facts, repairs) => {
492
- if (!facts || !repairs) return
493
- facts.hasBracketText = repairs.hasBracketText
494
- facts.hasEmphasis = repairs.hasEmphasis
495
- facts.hasLinkClose = repairs.hasLinkClose
496
- }
497
-
498
- const createBrokenRefScanState = () => {
499
- return { depth: 0, brokenEnd: false, tailOpen: -1 }
414
+ return getReferenceCount(state) > 0
500
415
  }
501
416
 
502
417
  const runInlineBrokenRefRepairStage = (children, facts, inlineContent, state) => {
503
418
  if (!shouldRunInlineBrokenRefRepair(facts, inlineContent, state)) return false
504
- const scanState = createBrokenRefScanState()
505
- const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
506
- if (maxRepairPass <= 0) return false
419
+ const scanState = { depth: 0, brokenEnd: false, tailOpen: -1 }
507
420
  const repairs = runBrokenRefRepairs(
508
421
  children,
509
- maxRepairPass,
510
422
  scanState,
511
- ensureInlineMetrics(facts, state),
423
+ getPostprocessMetrics(state),
512
424
  facts,
513
425
  BROKEN_REF_REPAIR_HOOKS
514
426
  )
515
- applyBrokenRefRepairFacts(facts, repairs)
427
+ facts.hasBracketText = repairs.hasBracketText
428
+ facts.hasEmphasis = repairs.hasEmphasis
429
+ facts.hasLinkClose = repairs.hasLinkClose
516
430
  return repairs.changed
517
431
  }
518
432
 
519
- const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) => {
433
+ const runInlineEmphasisRepairStage = (
434
+ children,
435
+ facts,
436
+ state,
437
+ isJapaneseMode,
438
+ forceBalanceSanitize = false
439
+ ) => {
520
440
  if (!facts.hasEmphasis) return false
521
441
  let changed = false
522
442
  const markChangedFrom = createInlineChangeMarker(facts)
523
- if (fixEmOuterStrongSequence(children, markChangedFrom)) changed = true
443
+ const metrics = getPostprocessMetrics(state)
444
+ const repairMask = forceBalanceSanitize
445
+ ? INLINE_REPAIR_ALL_EMPHASIS_FIXERS
446
+ : (facts.repairMask || 0)
447
+ if ((repairMask & INLINE_REPAIR_EM_OUTER_STRONG_SEQUENCE) &&
448
+ fixEmOuterStrongSequence(children, markChangedFrom)) {
449
+ changed = true
450
+ bumpPostprocessMetric(metrics, 'emphasisFixers', 'em-outer-strong-sequence')
451
+ }
524
452
  if (facts.hasLinkClose) {
525
- const metrics = ensureInlineMetrics(facts, state)
526
- if (fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) changed = true
527
- if (fixLeadingAsteriskEm(children, markChangedFrom)) changed = true
453
+ if ((repairMask & INLINE_REPAIR_TAIL_AFTER_LINK) &&
454
+ fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) {
455
+ changed = true
456
+ }
457
+ if ((repairMask & INLINE_REPAIR_LEADING_ASTERISK_EM) &&
458
+ fixLeadingAsteriskEm(children, markChangedFrom)) {
459
+ changed = true
460
+ bumpPostprocessMetric(metrics, 'emphasisFixers', 'leading-asterisk-em')
461
+ }
462
+ }
463
+ if ((repairMask & INLINE_REPAIR_TRAILING_STRONG) &&
464
+ fixTrailingStrong(children, markChangedFrom)) {
465
+ changed = true
466
+ bumpPostprocessMetric(metrics, 'emphasisFixers', 'trailing-strong')
467
+ }
468
+ const shouldAttemptSanitize = forceBalanceSanitize ||
469
+ changed ||
470
+ facts.hasAsteriskWrapperImbalance ||
471
+ (repairMask & INLINE_REPAIR_BALANCE_SANITIZE)
472
+ if (!shouldAttemptSanitize) {
473
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'skipped-balanced')
474
+ return changed
475
+ }
476
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'attempted')
477
+ if (forceBalanceSanitize || changed) {
478
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'attempted-after-change')
479
+ } else {
480
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'attempted-pre-scan-risk')
481
+ }
482
+ if (sanitizeEmStrongBalance(children, markChangedFrom)) {
483
+ changed = true
484
+ bumpPostprocessMetric(metrics, 'emphasisFixers', 'sanitize-em-strong-balance')
485
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'repaired')
486
+ } else {
487
+ bumpPostprocessMetric(metrics, 'emphasisSanitize', 'no-change')
528
488
  }
529
- if (fixTrailingStrong(children, markChangedFrom)) changed = true
530
- if (sanitizeEmStrongBalance(children, markChangedFrom)) changed = true
531
489
  return changed
532
490
  }
533
491
 
534
492
  const shouldRunInlineCollapsedRefRepair = (facts, state) => {
535
493
  if (!facts || !facts.hasBracketText) return false
536
- return ensureInlineReferenceCount(facts, state) > 0
537
- }
538
-
539
- const applyCollapsedRefRepairFacts = (facts) => {
540
- if (!facts) return
541
- facts.hasLinkOpen = true
542
- facts.hasLinkClose = true
543
- }
544
-
545
- const rewriteInlineCollapsedReferences = (children, facts, state, markChangedFrom) => {
546
- const changed = convertCollapsedReferenceLinks(
547
- children,
548
- state,
549
- facts,
550
- markChangedFrom
551
- )
552
- if (!changed) return false
553
- applyCollapsedRefRepairFacts(facts)
554
- return true
494
+ return getReferenceCount(state) > 0
555
495
  }
556
496
 
557
497
  const runInlineCollapsedRefStage = (children, facts, state) => {
558
498
  if (!shouldRunInlineCollapsedRefRepair(facts, state)) return false
559
499
  const markChangedFrom = createInlineChangeMarker(facts)
560
- if (!rewriteInlineCollapsedReferences(children, facts, state, markChangedFrom)) return false
500
+ if (!convertCollapsedReferenceLinks(children, state, facts, markChangedFrom)) return false
501
+ facts.hasLinkOpen = true
502
+ facts.hasLinkClose = true
561
503
  finalizeInlineLinkRepairStage(children, facts, markChangedFrom)
562
504
  return true
563
505
  }
564
506
 
565
- const shouldSkipInlinePostprocessToken = (children, facts, isJapaneseMode) => {
507
+ const shouldSkipInlinePostprocessToken = (facts, isJapaneseMode) => {
566
508
  if (!facts.hasEmphasis &&
567
509
  !facts.hasBracketText &&
568
510
  !facts.hasLinkOpen &&
569
511
  !facts.hasLinkClose &&
570
- !ensureInlineHasCodeInline(facts, children)) {
512
+ !facts.hasCodeInline) {
571
513
  return true
572
514
  }
573
- if (isJapaneseMode &&
574
- !hasJapaneseContextInRange(children, 0, children.length - 1)) {
515
+ if (isJapaneseMode && !facts.hasJapaneseContext) {
575
516
  return true
576
517
  }
577
518
  return false
@@ -598,7 +539,7 @@ const runInlineCoreRepairStages = (
598
539
  return false
599
540
  }
600
541
  if (runInlineBrokenRefRepairStage(children, facts, inlineContent, state)) changed = true
601
- if (runInlineEmphasisRepairStage(children, facts, state, isJapaneseMode)) changed = true
542
+ if (runInlineEmphasisRepairStage(children, facts, state, isJapaneseMode, changed)) changed = true
602
543
  return changed
603
544
  }
604
545
 
@@ -612,8 +553,8 @@ const processInlinePostprocessToken = (
612
553
  ) => {
613
554
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) return
614
555
  const children = token.children
615
- const facts = buildInlinePostprocessFacts(children, inlineContent)
616
- if (shouldSkipInlinePostprocessToken(children, facts, isJapaneseMode)) return
556
+ const facts = buildInlinePostprocessFacts(children, inlineContent, isJapaneseMode)
557
+ if (shouldSkipInlinePostprocessToken(facts, isJapaneseMode)) return
617
558
  const changed = runInlineCoreRepairStages(
618
559
  children,
619
560
  facts,
@@ -655,7 +596,7 @@ const registerTokenPostprocess = (md, baseOpt) => {
655
596
  md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
656
597
  if (!state || !state.tokens) return
657
598
  const overrideOpt = state.env && state.env.__strongJaTokenOpt
658
- const opt = overrideOpt ? getRuntimeOpt(state, baseOpt) : baseOpt
599
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(state, baseOpt) : baseOpt
659
600
  if (!opt.__strongJaPostprocessActive) return
660
601
  const isJapaneseMode = opt.__strongJaIsJapaneseMode
661
602
  const strictAsciiCodeGuard = opt.__strongJaStrictAsciiCodeGuard