@peaceroad/markdown-it-strong-ja 0.8.1 → 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.
package/README.md CHANGED
@@ -396,10 +396,10 @@ Supporting visuals:
396
396
 
397
397
  ## Notes
398
398
 
399
- - Use `state.env.__strongJaTokenOpt` to override options per render.
400
- - Overrides are merged with plugin options, but setup-time behavior (such as rule registration/order) cannot be switched at render time and cannot be retrofitted after the first `.use(...)` on the same `MarkdownIt` instance.
401
- - `mode` and `postprocess` are runtime-effective. `mditAttrs`, `patchCorePush`, and `coreRulesBeforePostprocess` are setup-time effective after the first `.use(...)` on a `MarkdownIt` instance.
399
+ - Use `state.env.__strongJaTokenOpt` to override runtime-effective options per render.
400
+ - Repeated `.use(...)` on the same `MarkdownIt` instance is treated as first-install-wins no-op. Create a new `MarkdownIt` instance for a different plugin option set.
401
+ - Runtime-effective override keys are merged with plugin options, but setup-time behavior (such as rule registration/order) cannot be switched at render time and cannot be retrofitted after the first `.use(...)` on the same `MarkdownIt` instance.
402
+ - `mode` and `postprocess` are runtime-effective via initial install or per-render override. `mditAttrs`, `patchCorePush`, and `coreRulesBeforePostprocess` are setup-time effective after the first `.use(...)` on a `MarkdownIt` instance.
402
403
  - This is an ESM plugin (`type: module`) and is tested against `markdown-it` 14.x in Node.js, browser bundlers, and VS Code extension pipelines that use `markdown-it` ESM.
403
404
  - The implementation relies on `markdown-it` internal ESM modules / core rule internals (`lib/token.mjs`, `lib/common/utils.mjs`, `ruler.__rules__`) plus a `scanDelims` prototype patch, so internal `markdown-it` changes may require plugin updates.
404
405
  - `scanDelims` patch is applied once per `MarkdownIt` prototype in the same process.
405
-
package/index.js CHANGED
@@ -19,31 +19,23 @@ const buildNormalizedOption = (md, option) => {
19
19
  return opt
20
20
  }
21
21
 
22
- const writeSharedOption = (target, source) => {
23
- for (const key of Object.keys(target)) {
24
- delete target[key]
25
- }
26
- Object.assign(target, source)
27
- return target
28
- }
29
-
30
22
  const mditStrongJa = (md, option) => {
31
23
  if (option && typeof option.engine === 'string' && option.engine !== 'token') {
32
24
  throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
33
25
  }
26
+ if (md.__strongJaTokenInstalled) {
27
+ return md
28
+ }
34
29
  const nextOpt = buildNormalizedOption(md, option)
35
- const opt = md.__strongJaTokenOpt && typeof md.__strongJaTokenOpt === 'object'
36
- ? writeSharedOption(md.__strongJaTokenOpt, nextOpt)
37
- : nextOpt
38
-
39
- md.__strongJaTokenOpt = opt
30
+ md.__strongJaTokenOpt = nextOpt
40
31
  patchScanDelims(md)
41
- registerTokenCompat(md, opt)
32
+ registerTokenCompat(md, nextOpt)
42
33
 
43
- registerTokenPostprocess(md, opt)
44
- ensureCoreRuleOrder(md, opt.__strongJaNormalizedCoreRulesBeforePostprocess, 'strong_ja_token_postprocess')
34
+ registerTokenPostprocess(md, nextOpt)
35
+ ensureCoreRuleOrder(md, nextOpt.__strongJaNormalizedCoreRulesBeforePostprocess, 'strong_ja_token_postprocess')
36
+ md.__strongJaTokenInstalled = true
45
37
 
46
38
  return md
47
39
  }
48
40
 
49
- export default mditStrongJa
41
+ export default mditStrongJa
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-strong-ja",
3
3
  "description": "Extends asterisk emphasis handling for Japanese text while keeping markdown-it behavior as close as practical.",
4
- "version": "0.8.1",
4
+ "version": "0.9.0",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "files": [
@@ -36,10 +36,10 @@
36
36
  "markdown-it": "^14.1.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.8",
40
- "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.10.0",
39
+ "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.10",
40
+ "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.11.0",
41
41
  "@peaceroad/markdown-it-renderer-image": "^0.12.0",
42
- "@peaceroad/markdown-it-renderer-inline-text": "^0.7.0",
42
+ "@peaceroad/markdown-it-renderer-inline-text": "^0.8.0",
43
43
  "markdown-it-attrs": "^4.3.1",
44
44
  "markdown-it-sub": "^2.0.0",
45
45
  "markdown-it-sup": "^2.0.0",
@@ -5,6 +5,7 @@ import {
5
5
  hasCjkBreaksRule,
6
6
  isCjkBreaksRuleName,
7
7
  getRuntimeOpt,
8
+ hasRuntimeOverride,
8
9
  moveRuleAfter
9
10
  } from './token-utils.js'
10
11
 
@@ -25,10 +26,14 @@ const trimTrailingSpaceTab = (text) => {
25
26
  return end === text.length ? text : text.slice(0, end)
26
27
  }
27
28
 
29
+ const getStateSource = (state) => {
30
+ return state && typeof state.src === 'string' ? state.src : ''
31
+ }
32
+
28
33
  const registerTokenCompat = (md, baseOpt) => {
29
34
  const isCompatibleMode = (state) => {
30
35
  const override = state && state.env && state.env.__strongJaTokenOpt
31
- if (!override) return baseOpt.__strongJaIsCompatibleMode === true
36
+ if (!hasRuntimeOverride(override)) return baseOpt.__strongJaIsCompatibleMode === true
32
37
  const opt = getRuntimeOpt(state, baseOpt)
33
38
  return opt.__strongJaIsCompatibleMode === true
34
39
  }
@@ -82,6 +87,8 @@ const registerTokenCompat = (md, baseOpt) => {
82
87
  const normalizeSoftbreakSpacing = (state) => {
83
88
  if (isCompatibleMode(state)) return
84
89
  if (!state) return
90
+ const src = getStateSource(state)
91
+ if (!src || src.indexOf('\n') === -1) return
85
92
  if (baseOpt.hasCjkBreaks !== true && state.md) {
86
93
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
87
94
  }
@@ -164,11 +171,8 @@ const registerTokenCompat = (md, baseOpt) => {
164
171
  const restoreSoftbreaksAfterCjk = (state) => {
165
172
  if (isCompatibleMode(state)) return
166
173
  if (!state) return
167
- const overrideOpt = state.env && state.env.__strongJaTokenOpt
168
- if (overrideOpt) {
169
- const opt = getRuntimeOpt(state, baseOpt)
170
- if (opt.mditAttrs !== false) return
171
- }
174
+ const src = getStateSource(state)
175
+ if (!src || src.indexOf('\n') === -1 || src.indexOf('{') === -1) return
172
176
  if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
173
177
  if (baseOpt.hasCjkBreaks !== true && state.md) {
174
178
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
@@ -239,11 +243,8 @@ const registerTokenCompat = (md, baseOpt) => {
239
243
  md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
240
244
  if (isCompatibleMode(state)) return
241
245
  if (!state || !state.tokens) return
242
- const overrideOpt = state.env && state.env.__strongJaTokenOpt
243
- if (overrideOpt) {
244
- const opt = getRuntimeOpt(state, baseOpt)
245
- if (opt.mditAttrs === false) return
246
- }
246
+ const src = getStateSource(state)
247
+ if (!src || src.indexOf('{') === -1 || src.indexOf('}') === -1) return
247
248
  for (let i = 0; i < state.tokens.length; i++) {
248
249
  const token = state.tokens[i]
249
250
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
package/src/token-core.js CHANGED
@@ -10,13 +10,15 @@ import {
10
10
  MODE_FLAG_COMPATIBLE,
11
11
  MODE_FLAG_AGGRESSIVE,
12
12
  MODE_FLAG_JAPANESE_PLUS,
13
- getRuntimeOpt
13
+ getRuntimeOpt,
14
+ hasRuntimeOverride
14
15
  } from './token-utils.js'
15
16
 
16
17
  const SCAN_DELIMS_PATCHED = Symbol.for('strongJaTokenScanDelimsPatched')
17
18
  const SINGLE_STAR_LOOKAROUND_MAX = 16
18
19
  const PREV_STAR_HAS_OPENER = 1
19
20
  const PREV_STAR_HAS_JP_BETWEEN = 2
21
+ const SCAN_DELIMS_LOOKUP_KEY = Symbol.for('strongJaTokenScanDelimsLookup')
20
22
 
21
23
  const isSoftSpaceCode = (code) => {
22
24
  return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_IDEOGRAPHIC_SPACE
@@ -280,7 +282,58 @@ const isAsciiGuardCloseWrapper = (code) => {
280
282
  code === 0x60 // `
281
283
  }
282
284
 
283
- const findPrevNonSpaceIndex = (src, start) => {
285
+ const buildScanDelimsLookupCache = (src) => {
286
+ const len = typeof src === 'string' ? src.length : 0
287
+ const prevNonSpaceSameLine = new Int32Array(len)
288
+ const nextNonSpaceSameLine = new Int32Array(len)
289
+ prevNonSpaceSameLine.fill(-1)
290
+ nextNonSpaceSameLine.fill(-1)
291
+
292
+ let prev = -1
293
+ for (let i = 0; i < len; i++) {
294
+ const code = src.charCodeAt(i)
295
+ if (code === CHAR_NEWLINE) {
296
+ prev = -1
297
+ continue
298
+ }
299
+ if (!isSoftSpaceCode(code)) prev = i
300
+ prevNonSpaceSameLine[i] = prev
301
+ }
302
+
303
+ let next = -1
304
+ for (let i = len - 1; i >= 0; i--) {
305
+ const code = src.charCodeAt(i)
306
+ if (code === CHAR_NEWLINE) {
307
+ next = -1
308
+ continue
309
+ }
310
+ if (!isSoftSpaceCode(code)) next = i
311
+ nextNonSpaceSameLine[i] = next
312
+ }
313
+
314
+ return {
315
+ src,
316
+ prevNonSpaceSameLine,
317
+ nextNonSpaceSameLine
318
+ }
319
+ }
320
+
321
+ const getScanDelimsLookupCache = (state) => {
322
+ if (!state || typeof state.src !== 'string') return null
323
+ const cached = state[SCAN_DELIMS_LOOKUP_KEY]
324
+ if (cached && cached.src === state.src) return cached
325
+ const next = buildScanDelimsLookupCache(state.src)
326
+ state[SCAN_DELIMS_LOOKUP_KEY] = next
327
+ return next
328
+ }
329
+
330
+ const findPrevNonSpaceIndex = (src, start, lookupCache = null) => {
331
+ if (start < 0) return -1
332
+ if (lookupCache &&
333
+ lookupCache.src === src &&
334
+ start < lookupCache.prevNonSpaceSameLine.length) {
335
+ return lookupCache.prevNonSpaceSameLine[start]
336
+ }
284
337
  for (let i = start; i >= 0; i--) {
285
338
  const code = src.charCodeAt(i)
286
339
  if (code === CHAR_NEWLINE) return -1
@@ -290,7 +343,14 @@ const findPrevNonSpaceIndex = (src, start) => {
290
343
  return -1
291
344
  }
292
345
 
293
- const findNextNonSpaceIndex = (src, start, max) => {
346
+ const findNextNonSpaceIndex = (src, start, max, lookupCache = null) => {
347
+ if (lookupCache &&
348
+ lookupCache.src === src &&
349
+ start >= 0 &&
350
+ start < lookupCache.nextNonSpaceSameLine.length) {
351
+ const next = lookupCache.nextNonSpaceSameLine[start]
352
+ return next !== -1 && next < max ? next : -1
353
+ }
294
354
  for (let i = start; i < max; i++) {
295
355
  const code = src.charCodeAt(i)
296
356
  if (code === CHAR_NEWLINE) return -1
@@ -300,26 +360,26 @@ const findNextNonSpaceIndex = (src, start, max) => {
300
360
  return -1
301
361
  }
302
362
 
303
- const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max) => {
363
+ const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max, lookupCache = null) => {
304
364
  let i = index
305
365
  // Two wrappers are enough for common shapes: * [ "word" ]*
306
366
  for (let wrappers = 0; wrappers < 2 && i >= 0 && i < max; wrappers++) {
307
367
  const code = src.charCodeAt(i)
308
368
  if (!isAsciiGuardOpenWrapper(code)) break
309
- i = findNextNonSpaceIndex(src, i + 1, max)
369
+ i = findNextNonSpaceIndex(src, i + 1, max, lookupCache)
310
370
  if (i === -1) return false
311
371
  }
312
372
  if (i < 0 || i >= max) return false
313
373
  return isAsciiAlphaNum(src.charCodeAt(i))
314
374
  }
315
375
 
316
- const hasAsciiEndBeforeOptionalCloseWrappers = (src, index) => {
376
+ const hasAsciiEndBeforeOptionalCloseWrappers = (src, index, lookupCache = null) => {
317
377
  let i = index
318
378
  // Two wrappers are enough for common shapes: *["word"] *
319
379
  for (let wrappers = 0; wrappers < 2 && i >= 0; wrappers++) {
320
380
  const code = src.charCodeAt(i)
321
381
  if (!isAsciiGuardCloseWrapper(code)) break
322
- i = findPrevNonSpaceIndex(src, i - 1)
382
+ i = findPrevNonSpaceIndex(src, i - 1, lookupCache)
323
383
  if (i === -1) return false
324
384
  }
325
385
  if (i < 0) return false
@@ -353,7 +413,17 @@ const isSentenceBoundaryStop = (code) => {
353
413
  code === 0x2049 // ⁉
354
414
  }
355
415
 
356
- const findPrevNonSpaceLimited = (src, start, maxLook) => {
416
+ const findPrevNonSpaceLimited = (src, start, maxLook, lookupCache = null) => {
417
+ if (lookupCache &&
418
+ lookupCache.src === src &&
419
+ start >= 0 &&
420
+ start < lookupCache.prevNonSpaceSameLine.length) {
421
+ const prev = lookupCache.prevNonSpaceSameLine[start]
422
+ if (prev !== -1 && (start - prev) < maxLook) {
423
+ return src.charCodeAt(prev)
424
+ }
425
+ return 0
426
+ }
357
427
  let looked = 0
358
428
  for (let i = start; i >= 0; i--) {
359
429
  if (looked >= maxLook) break
@@ -366,7 +436,17 @@ const findPrevNonSpaceLimited = (src, start, maxLook) => {
366
436
  return 0
367
437
  }
368
438
 
369
- const findNextNonSpaceLimited = (src, start, max, maxLook) => {
439
+ const findNextNonSpaceLimited = (src, start, max, maxLook, lookupCache = null) => {
440
+ if (lookupCache &&
441
+ lookupCache.src === src &&
442
+ start >= 0 &&
443
+ start < lookupCache.nextNonSpaceSameLine.length) {
444
+ const next = lookupCache.nextNonSpaceSameLine[start]
445
+ if (next !== -1 && next < max && (next - start) < maxLook) {
446
+ return src.charCodeAt(next)
447
+ }
448
+ return 0
449
+ }
370
450
  let looked = 0
371
451
  for (let i = start; i < max; i++) {
372
452
  if (looked >= maxLook) break
@@ -379,13 +459,13 @@ const findNextNonSpaceLimited = (src, start, max, maxLook) => {
379
459
  return 0
380
460
  }
381
461
 
382
- const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar) => {
462
+ const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar, lookupCache = null) => {
383
463
  if (isWrapperOpenLike(nextChar)) {
384
- const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX)
464
+ const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
385
465
  if (isJapaneseChar(right)) return true
386
466
  }
387
467
  if (isWrapperCloseLike(lastChar)) {
388
- const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX)
468
+ const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
389
469
  if (isJapaneseChar(left)) return true
390
470
  }
391
471
  return false
@@ -415,7 +495,8 @@ const scanPrevSingleStarContextFlags = (src, start) => {
415
495
  }
416
496
 
417
497
  const ensurePrevStarFlags = (src, start, prevStarFlags) => {
418
- return prevStarFlags >= 0 ? prevStarFlags : scanPrevSingleStarContextFlags(src, start)
498
+ if (prevStarFlags >= 0) return prevStarFlags
499
+ return scanPrevSingleStarContextFlags(src, start)
419
500
  }
420
501
 
421
502
  const fixTrailingStrong = (tokens, onChangeStart = null) => {
@@ -696,7 +777,7 @@ const patchScanDelims = (md) => {
696
777
 
697
778
  const baseOpt = this.md ? this.md.__strongJaTokenOpt : null
698
779
  const overrideOpt = this.env && this.env.__strongJaTokenOpt
699
- const opt = overrideOpt ? getRuntimeOpt(this, baseOpt) : baseOpt
780
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(this, baseOpt) : baseOpt
700
781
  if (!opt) {
701
782
  return base
702
783
  }
@@ -707,6 +788,7 @@ const patchScanDelims = (md) => {
707
788
  const plusMode = (modeFlags & MODE_FLAG_JAPANESE_PLUS) !== 0
708
789
  const aggressiveMode = (modeFlags & MODE_FLAG_AGGRESSIVE) !== 0
709
790
  const max = this.posMax
791
+ let lookupCache = null
710
792
  const lastChar = start > 0 ? src.charCodeAt(start - 1) : 0x20
711
793
 
712
794
  const count = base && base.length ? base.length : 1
@@ -719,7 +801,15 @@ const patchScanDelims = (md) => {
719
801
  const rightJapanese = isJapaneseChar(nextChar)
720
802
  let hasJapaneseContext = leftJapanese || rightJapanese
721
803
  if (!hasJapaneseContext && count === 1) {
722
- hasJapaneseContext = hasJapaneseContextForBracketWrapper(src, start, pos, max, lastChar, nextChar)
804
+ hasJapaneseContext = hasJapaneseContextForBracketWrapper(
805
+ src,
806
+ start,
807
+ pos,
808
+ max,
809
+ lastChar,
810
+ nextChar,
811
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
812
+ )
723
813
  }
724
814
  if (!hasJapaneseContext && count === 1 && isExtraSingleStarClosePunct(lastChar)) {
725
815
  prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
@@ -734,22 +824,31 @@ const patchScanDelims = (md) => {
734
824
  let isLastWhiteSpace = isWhiteSpace(lastChar) || isSoftSpaceCode(lastChar)
735
825
  let isNextWhiteSpace = isWhiteSpace(nextChar) || isSoftSpaceCode(nextChar)
736
826
  if (isLastWhiteSpace && isSoftSpaceCode(lastChar)) {
737
- const prevNonSpaceIdx = findPrevNonSpaceIndex(src, start - 2)
827
+ const prevNonSpaceIdx = findPrevNonSpaceIndex(
828
+ src,
829
+ start - 2,
830
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
831
+ )
738
832
  if (prevNonSpaceIdx !== -1) {
739
833
  const prevNonSpaceLocal = src.charCodeAt(prevNonSpaceIdx)
740
834
  const plusStrictAsciiBoundary = plusMode &&
741
- hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx)
835
+ hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx, lookupCache)
742
836
  if (prevNonSpaceLocal !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
743
837
  isLastWhiteSpace = false
744
838
  }
745
839
  }
746
840
  }
747
841
  if (isNextWhiteSpace && isSoftSpaceCode(nextChar)) {
748
- const nextNonSpaceIdx = findNextNonSpaceIndex(src, pos, max)
842
+ const nextNonSpaceIdx = findNextNonSpaceIndex(
843
+ src,
844
+ pos,
845
+ max,
846
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
847
+ )
749
848
  if (nextNonSpaceIdx !== -1) {
750
849
  const nextNonSpace = src.charCodeAt(nextNonSpaceIdx)
751
850
  const plusStrictAsciiBoundary = plusMode &&
752
- hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max)
851
+ hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max, lookupCache)
753
852
  if (nextNonSpace !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
754
853
  isNextWhiteSpace = false
755
854
  }
@@ -120,10 +120,7 @@ const cloneTextToken = (source, content) => {
120
120
  }
121
121
 
122
122
  const applyBracketSegmentFlags = (token, seg) => {
123
- if (seg === '[' || seg === ']') {
124
- token.__strongJaHasBracket = true
125
- token.__strongJaBracketAtomic = true
126
- } else if (seg === '[]') {
123
+ if (seg === '[' || seg === ']' || seg === '[]') {
127
124
  token.__strongJaHasBracket = true
128
125
  token.__strongJaBracketAtomic = true
129
126
  } else {
@@ -641,16 +638,21 @@ const collectBrokenMarkLinkMergeRemovals = (tokens) => {
641
638
 
642
639
  const applyBrokenMarkLinkMergeRemovals = (tokens, removals, onChangeStart = null) => {
643
640
  if (!removals || removals.length === 0) return false
644
- const removeFlags = new Array(tokens.length).fill(false)
645
641
  for (let idx = removals.length - 1; idx >= 0; idx--) {
646
- const removal = removals[idx]
647
- if (onChangeStart) onChangeStart(removal.closeIdx)
648
- removeFlags[removal.closeIdx] = true
649
- removeFlags[removal.reopenIdx] = true
642
+ if (onChangeStart) onChangeStart(removals[idx].closeIdx)
650
643
  }
651
644
  const kept = []
645
+ let removalIdx = 0
646
+ let nextRemoval = removals[removalIdx]
652
647
  for (let idx = 0; idx < tokens.length; idx++) {
653
- if (!removeFlags[idx]) kept.push(tokens[idx])
648
+ if (nextRemoval && (idx === nextRemoval.closeIdx || idx === nextRemoval.reopenIdx)) {
649
+ if (idx === nextRemoval.reopenIdx) {
650
+ removalIdx++
651
+ nextRemoval = removals[removalIdx]
652
+ }
653
+ continue
654
+ }
655
+ kept.push(tokens[idx])
654
656
  }
655
657
  tokens.splice(0, tokens.length, ...kept)
656
658
  return true
@@ -1,7 +1,7 @@
1
1
  import { buildLinkCloseMap } from '../token-link-utils.js'
2
2
  import {
3
3
  isAsteriskEmphasisToken,
4
- hasTextMarkerCharsInRange,
4
+ buildBrokenRefWrapperRangeSignals,
5
5
  buildAsteriskWrapperPrefixStats,
6
6
  shouldAttemptBrokenRefRewrite
7
7
  } from './guards.js'
@@ -194,7 +194,13 @@ const resolveBrokenRefCandidateGuardFlow = (
194
194
  hooks = null,
195
195
  fallbackCache = null
196
196
  ) => {
197
- if (!hasTextMarkerCharsInRange(children, brokenRefCandidate.start, segmentEnd, brokenRefCandidate.startTextOffset)) {
197
+ const wrapperSignals = buildBrokenRefWrapperRangeSignals(
198
+ children,
199
+ brokenRefCandidate.start,
200
+ segmentEnd,
201
+ brokenRefCandidate.startTextOffset
202
+ )
203
+ if (!wrapperSignals.hasTextMarker) {
198
204
  return BROKEN_REF_FLOW_SKIP_NO_TEXT_MARKER
199
205
  }
200
206
  const wrapperPrefixStats = ensureBrokenRefWrapperPrefixStats(children, facts, hooks, fallbackCache)
@@ -203,7 +209,8 @@ const resolveBrokenRefCandidateGuardFlow = (
203
209
  brokenRefCandidate.start,
204
210
  segmentEnd,
205
211
  brokenRefCandidate.startTextOffset,
206
- wrapperPrefixStats
212
+ wrapperPrefixStats,
213
+ wrapperSignals
207
214
  )) {
208
215
  return BROKEN_REF_FLOW_SKIP_GUARD
209
216
  }
@@ -1,17 +1,11 @@
1
1
  import { isJapaneseChar } from '../token-utils.js'
2
2
 
3
+ const CHAR_ASTERISK = 0x2A // *
4
+
3
5
  const hasMarkerChars = (text) => {
4
6
  return !!text && text.indexOf('*') !== -1
5
7
  }
6
8
 
7
- const contentHasMarkerCharsFrom = (content, from) => {
8
- if (!content) return false
9
- const start = from > 0 ? from : 0
10
- if (start === 0) return hasMarkerChars(content)
11
- if (start >= content.length) return false
12
- return content.indexOf('*', start) !== -1
13
- }
14
-
15
9
  const isAsteriskEmphasisToken = (token) => {
16
10
  if (!token || !token.type) return false
17
11
  if (token.type !== 'strong_open' &&
@@ -78,20 +72,6 @@ const hasEmphasisSignalInRange = (tokens, startIdx, endIdx) => {
78
72
  return false
79
73
  }
80
74
 
81
- const hasTextMarkerCharsInRange = (tokens, startIdx, endIdx, firstTextOffset = 0) => {
82
- if (!tokens || startIdx < 0 || endIdx < startIdx) return false
83
- for (let i = startIdx; i <= endIdx && i < tokens.length; i++) {
84
- const token = tokens[i]
85
- if (!token || token.type !== 'text' || !token.content) continue
86
- if (i === startIdx && firstTextOffset > 0) {
87
- if (contentHasMarkerCharsFrom(token.content, firstTextOffset)) return true
88
- continue
89
- }
90
- if (textTokenHasMarkerChars(token)) return true
91
- }
92
- return false
93
- }
94
-
95
75
  const isStrongRunSoftSpace = (code) => {
96
76
  return code === 0x20 || code === 0x09 || code === 0x0A || code === 0x3000
97
77
  }
@@ -107,21 +87,24 @@ const isStrongRunTextLike = (code) => {
107
87
  return isStrongRunAsciiWord(code) || isJapaneseChar(code)
108
88
  }
109
89
 
110
- const countDelimiterLikeStrongRuns = (content, marker, from = 0, limit = 0) => {
90
+ const countDelimiterLikeStrongRuns = (content, from = 0, limit = 0) => {
111
91
  let at = from > 0 ? from : 0
112
92
  const len = content.length
113
- const markerCode = marker.charCodeAt(0)
114
93
  let count = 0
115
- while (at < len) {
116
- const pos = content.indexOf(marker, at)
117
- if (pos === -1) break
94
+ while (at + 1 < len) {
95
+ if (content.charCodeAt(at) !== CHAR_ASTERISK ||
96
+ content.charCodeAt(at + 1) !== CHAR_ASTERISK) {
97
+ at++
98
+ continue
99
+ }
100
+ const pos = at
118
101
  const prevCode = pos > 0 ? content.charCodeAt(pos - 1) : 0
119
- const nextPos = pos + marker.length
102
+ const nextPos = pos + 2
120
103
  const nextCode = nextPos < len ? content.charCodeAt(nextPos) : 0
121
- const prevSameMarker = prevCode === markerCode
122
- const nextSameMarker = nextCode === markerCode
104
+ const prevSameMarker = prevCode === CHAR_ASTERISK
105
+ const nextSameMarker = nextCode === CHAR_ASTERISK
123
106
  if (prevSameMarker || nextSameMarker) {
124
- at = pos + marker.length
107
+ at = pos + 2
125
108
  continue
126
109
  }
127
110
  const prevSoft = prevCode !== 0 && isStrongRunSoftSpace(prevCode)
@@ -131,92 +114,50 @@ const countDelimiterLikeStrongRuns = (content, marker, from = 0, limit = 0) => {
131
114
  const nextTextLike = isStrongRunTextLike(nextCode)
132
115
  const hasTextNeighbor = prevTextLike || nextTextLike
133
116
  if (!hasTextNeighbor) {
134
- at = pos + marker.length
117
+ at = pos + 2
135
118
  continue
136
119
  }
137
120
  const atBoundary = prevCode === 0 || nextCode === 0
138
121
  if (!atBoundary && (!prevTextLike || !nextTextLike)) {
139
- at = pos + marker.length
122
+ at = pos + 2
140
123
  continue
141
124
  }
142
125
  if (hasPrevOrNext && !prevSoft && !nextSoft) {
143
126
  count++
144
127
  if (limit > 0 && count >= limit) return count
145
128
  }
146
- at = pos + marker.length
129
+ at = pos + 2
147
130
  }
148
131
  return count
149
132
  }
150
133
 
151
- const countStrongMarkerRunsInTextRange = (tokens, startIdx, endIdx, firstTextOffset = 0, limit = 0) => {
152
- if (!tokens || startIdx < 0 || endIdx < startIdx) return 0
153
- let total = 0
154
- for (let i = startIdx; i <= endIdx && i < tokens.length; i++) {
155
- const token = tokens[i]
156
- if (!token || token.type !== 'text' || !token.content) continue
157
- const content = token.content
158
- const scanFrom = i === startIdx && firstTextOffset > 0 ? firstTextOffset : 0
159
- if (scanFrom >= content.length) continue
160
- const remain = limit > 0 ? (limit - total) : 0
161
- total += countDelimiterLikeStrongRuns(content, '**', scanFrom, remain)
162
- if (limit > 0 && total >= limit) {
163
- return total
164
- }
165
- }
166
- return total
167
- }
168
-
169
134
  const buildAsteriskWrapperPrefixStats = (tokens) => {
170
135
  const len = Array.isArray(tokens) ? tokens.length : 0
171
136
  const strongDepthPrefix = new Array(len + 1)
172
137
  const emDepthPrefix = new Array(len + 1)
173
- const strongOpenPrefix = new Array(len + 1)
174
- const strongClosePrefix = new Array(len + 1)
175
- const emOpenPrefix = new Array(len + 1)
176
- const emClosePrefix = new Array(len + 1)
177
138
  let strongDepth = 0
178
139
  let emDepthCount = 0
179
- let strongOpenCount = 0
180
- let strongCloseCount = 0
181
- let emOpenCount = 0
182
- let emCloseCount = 0
183
140
  strongDepthPrefix[0] = 0
184
141
  emDepthPrefix[0] = 0
185
- strongOpenPrefix[0] = 0
186
- strongClosePrefix[0] = 0
187
- emOpenPrefix[0] = 0
188
- emClosePrefix[0] = 0
189
142
  for (let i = 0; i < len; i++) {
190
143
  const token = tokens[i]
191
144
  if (token && token.type && isAsteriskEmphasisToken(token)) {
192
145
  if (token.type === 'strong_open') {
193
146
  strongDepth++
194
- strongOpenCount++
195
147
  } else if (token.type === 'strong_close') {
196
148
  if (strongDepth > 0) strongDepth--
197
- strongCloseCount++
198
149
  } else if (token.type === 'em_open') {
199
150
  emDepthCount++
200
- emOpenCount++
201
151
  } else if (token.type === 'em_close') {
202
152
  if (emDepthCount > 0) emDepthCount--
203
- emCloseCount++
204
153
  }
205
154
  }
206
155
  strongDepthPrefix[i + 1] = strongDepth
207
156
  emDepthPrefix[i + 1] = emDepthCount
208
- strongOpenPrefix[i + 1] = strongOpenCount
209
- strongClosePrefix[i + 1] = strongCloseCount
210
- emOpenPrefix[i + 1] = emOpenCount
211
- emClosePrefix[i + 1] = emCloseCount
212
157
  }
213
158
  return {
214
159
  strongDepth: strongDepthPrefix,
215
- emDepth: emDepthPrefix,
216
- strongOpen: strongOpenPrefix,
217
- strongClose: strongClosePrefix,
218
- emOpen: emOpenPrefix,
219
- emClose: emClosePrefix
160
+ emDepth: emDepthPrefix
220
161
  }
221
162
  }
222
163
 
@@ -229,6 +170,8 @@ const createBrokenRefWrapperRangeSignals = () => {
229
170
  hasUnderscoreText: false,
230
171
  hasCodeInline: false,
231
172
  hasUnderscoreEmphasisToken: false,
173
+ hasTextMarker: false,
174
+ strongRunCount: 0,
232
175
  strongOpenInRange: 0,
233
176
  strongCloseInRange: 0,
234
177
  emOpenInRange: 0,
@@ -239,17 +182,25 @@ const createBrokenRefWrapperRangeSignals = () => {
239
182
  const updateBrokenRefTextRangeSignals = (signals, token, tokenIdx, startIdx, firstTextOffset) => {
240
183
  if (!token || token.type !== 'text' || !token.content) return
241
184
  const content = token.content
185
+ const scanFrom = tokenIdx === startIdx && firstTextOffset > 0 ? firstTextOffset : 0
242
186
  // Keep this at 0 (instead of firstTextOffset) so historical fail-safe
243
187
  // behavior around noisy leading chains in the first text token stays unchanged.
244
188
  if (!signals.hasLongStarNoise && content.indexOf('***') !== -1) {
245
189
  signals.hasLongStarNoise = true
246
190
  }
247
191
  if (!signals.hasUnderscoreText) {
248
- const scanFrom = tokenIdx === startIdx && firstTextOffset > 0 ? firstTextOffset : 0
249
192
  if (scanFrom < content.length && content.indexOf('_', scanFrom) !== -1) {
250
193
  signals.hasUnderscoreText = true
251
194
  }
252
195
  }
196
+ if (!signals.hasTextMarker) {
197
+ signals.hasTextMarker = scanFrom === 0
198
+ ? textTokenHasMarkerChars(token)
199
+ : content.indexOf('*', scanFrom) !== -1
200
+ }
201
+ if (signals.strongRunCount < 2 && scanFrom < content.length) {
202
+ signals.strongRunCount += countDelimiterLikeStrongRuns(content, scanFrom, 2 - signals.strongRunCount)
203
+ }
253
204
  }
254
205
 
255
206
  const updateBrokenRefWrapperTokenSignals = (signals, token, isAsteriskEmphasis) => {
@@ -343,18 +294,10 @@ const hasPreexistingWrapperCloseOnlyInRange = (tokens, startIdx, endIdx, prefixS
343
294
  const hasPrefix =
344
295
  !!prefixStats &&
345
296
  Array.isArray(prefixStats.strongDepth) &&
346
- Array.isArray(prefixStats.emDepth) &&
347
- Array.isArray(prefixStats.strongOpen) &&
348
- Array.isArray(prefixStats.strongClose) &&
349
- Array.isArray(prefixStats.emOpen) &&
350
- Array.isArray(prefixStats.emClose)
297
+ Array.isArray(prefixStats.emDepth)
351
298
  if (hasPrefix &&
352
299
  startIdx < prefixStats.strongDepth.length &&
353
- startIdx < prefixStats.emDepth.length &&
354
- (endIdx + 1) < prefixStats.strongOpen.length &&
355
- (endIdx + 1) < prefixStats.strongClose.length &&
356
- (endIdx + 1) < prefixStats.emOpen.length &&
357
- (endIdx + 1) < prefixStats.emClose.length) {
300
+ startIdx < prefixStats.emDepth.length) {
358
301
  if (needsStrongCloseOnly) {
359
302
  preStrongDepth = prefixStats.strongDepth[startIdx] || 0
360
303
  if (preStrongDepth > 0) return true
@@ -434,8 +377,8 @@ const isLowConfidenceBrokenRefRange = (tokens, startIdx, endIdx, firstTextOffset
434
377
  return hasBrokenRefLowConfidenceWrapperRisk(tokens, startIdx, endIdx, wrapperPrefixStats, signals)
435
378
  }
436
379
 
437
- const hasBrokenRefStrongRunEvidence = (tokens, startIdx, endIdx, firstTextOffset = 0) => {
438
- return countStrongMarkerRunsInTextRange(tokens, startIdx, endIdx, firstTextOffset, 2) >= 2
380
+ const hasBrokenRefStrongRunEvidence = (wrapperSignals) => {
381
+ return !!wrapperSignals && wrapperSignals.strongRunCount >= 2
439
382
  }
440
383
 
441
384
  const hasBrokenRefExplicitAsteriskSignal = (wrapperSignals) => {
@@ -450,22 +393,31 @@ const shouldRejectBalancedBrokenRefRewrite = (wrapperSignals) => {
450
393
  return !wrapperSignals.hasImbalance && hasBrokenRefExplicitAsteriskSignal(wrapperSignals)
451
394
  }
452
395
 
453
- const shouldAttemptBrokenRefRewriteFromSignals = (tokens, startIdx, endIdx, firstTextOffset, wrapperSignals) => {
396
+ const shouldAttemptBrokenRefRewriteFromSignals = (wrapperSignals) => {
454
397
  if (hasBrokenRefImmediateRewriteSignal(wrapperSignals)) return true
455
398
  if (shouldRejectBalancedBrokenRefRewrite(wrapperSignals)) return false
456
- return hasBrokenRefStrongRunEvidence(tokens, startIdx, endIdx, firstTextOffset)
399
+ return hasBrokenRefStrongRunEvidence(wrapperSignals)
457
400
  }
458
401
 
459
- const shouldAttemptBrokenRefRewrite = (tokens, startIdx, endIdx, firstTextOffset = 0, wrapperPrefixStats = null) => {
460
- const wrapperSignals = buildBrokenRefWrapperRangeSignals(tokens, startIdx, endIdx, firstTextOffset)
461
- if (isLowConfidenceBrokenRefRange(tokens, startIdx, endIdx, firstTextOffset, wrapperPrefixStats, wrapperSignals)) return false
462
- return shouldAttemptBrokenRefRewriteFromSignals(tokens, startIdx, endIdx, firstTextOffset, wrapperSignals)
402
+ const shouldAttemptBrokenRefRewrite = (
403
+ tokens,
404
+ startIdx,
405
+ endIdx,
406
+ firstTextOffset = 0,
407
+ wrapperPrefixStats = null,
408
+ wrapperSignals = null
409
+ ) => {
410
+ const signals = wrapperSignals || buildBrokenRefWrapperRangeSignals(tokens, startIdx, endIdx, firstTextOffset)
411
+ if (!signals.hasTextMarker) return false
412
+ if (isLowConfidenceBrokenRefRange(tokens, startIdx, endIdx, firstTextOffset, wrapperPrefixStats, signals)) return false
413
+ return shouldAttemptBrokenRefRewriteFromSignals(signals)
463
414
  }
464
415
 
465
416
  const scanInlinePostprocessSignals = (children) => {
466
417
  let hasEmphasis = false
467
418
  let hasLinkOpen = false
468
419
  let hasLinkClose = false
420
+ let hasCodeInline = false
469
421
  for (let j = 0; j < children.length; j++) {
470
422
  const child = children[j]
471
423
  if (!child) continue
@@ -478,12 +430,16 @@ const scanInlinePostprocessSignals = (children) => {
478
430
  if (!hasLinkClose && child.type === 'link_close') {
479
431
  hasLinkClose = true
480
432
  }
433
+ if (!hasCodeInline && child.type === 'code_inline') {
434
+ hasCodeInline = true
435
+ }
481
436
  if (hasEmphasis && hasLinkOpen && hasLinkClose) break
482
437
  }
483
438
  return {
484
439
  hasEmphasis,
485
440
  hasLinkOpen,
486
- hasLinkClose
441
+ hasLinkClose,
442
+ hasCodeInline
487
443
  }
488
444
  }
489
445
 
@@ -492,8 +448,8 @@ export {
492
448
  isAsteriskEmphasisToken,
493
449
  hasJapaneseContextInRange,
494
450
  hasEmphasisSignalInRange,
495
- hasTextMarkerCharsInRange,
496
451
  buildAsteriskWrapperPrefixStats,
452
+ buildBrokenRefWrapperRangeSignals,
497
453
  shouldAttemptBrokenRefRewrite,
498
454
  scanInlinePostprocessSignals
499
455
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from '../token-core.js'
11
11
  import {
12
12
  getRuntimeOpt,
13
+ hasRuntimeOverride,
13
14
  getReferenceCount
14
15
  } from '../token-utils.js'
15
16
  import {
@@ -87,46 +88,13 @@ const buildInlinePostprocessFacts = (children, inlineContent) => {
87
88
  hasEmphasis: preScan.hasEmphasis,
88
89
  hasLinkOpen: preScan.hasLinkOpen,
89
90
  hasLinkClose: preScan.hasLinkClose,
90
- hasCodeInline: undefined,
91
- referenceCount: undefined,
92
- metrics: undefined,
91
+ hasCodeInline: preScan.hasCodeInline,
93
92
  linkCloseMap: undefined,
94
93
  wrapperPrefixStats: undefined,
95
94
  rebuildLevelStart: undefined
96
95
  }
97
96
  }
98
97
 
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
98
  const ensureInlineLinkCloseMap = (facts, tokens) => {
131
99
  if (!tokens || tokens.length === 0) return new Map()
132
100
  if (!facts) return buildLinkCloseMap(tokens, 0, tokens.length - 1)
@@ -281,6 +249,9 @@ const isSoftSpaceCode = (code) => {
281
249
  return code === 0x20 || code === 0x09 || code === 0x3000
282
250
  }
283
251
 
252
+ const CHAR_ASTERISK = 0x2A // *
253
+ const CHAR_BACKSLASH = 0x5C // \
254
+
284
255
  const isAsciiWordCode = (code) => {
285
256
  return (code >= 0x30 && code <= 0x39) ||
286
257
  (code >= 0x41 && code <= 0x5A) ||
@@ -299,7 +270,7 @@ const textStartsAsciiWord = (text) => {
299
270
 
300
271
  const isEscapedMarkerAt = (content, index) => {
301
272
  let slashCount = 0
302
- 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--) {
303
274
  slashCount++
304
275
  }
305
276
  return (slashCount % 2) === 1
@@ -307,24 +278,24 @@ const isEscapedMarkerAt = (content, index) => {
307
278
 
308
279
  const findLastStandaloneStrongMarker = (content) => {
309
280
  if (!content || content.length < 2) return -1
310
- let pos = content.lastIndexOf('**')
311
- 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
+ }
312
286
  const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
313
287
  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)
288
+ if (prev === CHAR_ASTERISK || next === CHAR_ASTERISK) continue
289
+ if (prev === CHAR_BACKSLASH && isEscapedMarkerAt(content, pos)) continue
290
+ return pos
320
291
  }
321
292
  return -1
322
293
  }
323
294
 
324
295
  const hasLeadingStandaloneStrongMarker = (content) => {
325
296
  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
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
328
299
  return true
329
300
  }
330
301
 
@@ -474,7 +445,7 @@ const tryActivateInlineEmphasis = (
474
445
  return true
475
446
  }
476
447
  if (facts.hasBracketText || facts.hasLinkOpen || facts.hasLinkClose) return false
477
- if (!ensureInlineHasCodeInline(facts, children)) return false
448
+ if (!facts.hasCodeInline) return false
478
449
  if (tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard, facts)) {
479
450
  facts.hasEmphasis = true
480
451
  return true
@@ -485,7 +456,7 @@ const tryActivateInlineEmphasis = (
485
456
  const shouldRunInlineBrokenRefRepair = (facts, inlineContent, state) => {
486
457
  if (!facts || !facts.hasLinkOpen || !facts.hasLinkClose || !facts.hasBracketText) return false
487
458
  if (inlineContent.indexOf('***') !== -1) return false
488
- return ensureInlineReferenceCount(facts, state) > 0
459
+ return getReferenceCount(state) > 0
489
460
  }
490
461
 
491
462
  const applyBrokenRefRepairFacts = (facts, repairs) => {
@@ -508,7 +479,7 @@ const runInlineBrokenRefRepairStage = (children, facts, inlineContent, state) =>
508
479
  children,
509
480
  maxRepairPass,
510
481
  scanState,
511
- ensureInlineMetrics(facts, state),
482
+ getPostprocessMetrics(state),
512
483
  facts,
513
484
  BROKEN_REF_REPAIR_HOOKS
514
485
  )
@@ -522,7 +493,7 @@ const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) =>
522
493
  const markChangedFrom = createInlineChangeMarker(facts)
523
494
  if (fixEmOuterStrongSequence(children, markChangedFrom)) changed = true
524
495
  if (facts.hasLinkClose) {
525
- const metrics = ensureInlineMetrics(facts, state)
496
+ const metrics = getPostprocessMetrics(state)
526
497
  if (fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) changed = true
527
498
  if (fixLeadingAsteriskEm(children, markChangedFrom)) changed = true
528
499
  }
@@ -533,7 +504,7 @@ const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) =>
533
504
 
534
505
  const shouldRunInlineCollapsedRefRepair = (facts, state) => {
535
506
  if (!facts || !facts.hasBracketText) return false
536
- return ensureInlineReferenceCount(facts, state) > 0
507
+ return getReferenceCount(state) > 0
537
508
  }
538
509
 
539
510
  const applyCollapsedRefRepairFacts = (facts) => {
@@ -567,7 +538,7 @@ const shouldSkipInlinePostprocessToken = (children, facts, isJapaneseMode) => {
567
538
  !facts.hasBracketText &&
568
539
  !facts.hasLinkOpen &&
569
540
  !facts.hasLinkClose &&
570
- !ensureInlineHasCodeInline(facts, children)) {
541
+ !facts.hasCodeInline) {
571
542
  return true
572
543
  }
573
544
  if (isJapaneseMode &&
@@ -655,7 +626,7 @@ const registerTokenPostprocess = (md, baseOpt) => {
655
626
  md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
656
627
  if (!state || !state.tokens) return
657
628
  const overrideOpt = state.env && state.env.__strongJaTokenOpt
658
- const opt = overrideOpt ? getRuntimeOpt(state, baseOpt) : baseOpt
629
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(state, baseOpt) : baseOpt
659
630
  if (!opt.__strongJaPostprocessActive) return
660
631
  const isJapaneseMode = opt.__strongJaIsJapaneseMode
661
632
  const strictAsciiCodeGuard = opt.__strongJaStrictAsciiCodeGuard
@@ -8,6 +8,7 @@ const MODE_FLAG_AGGRESSIVE = 1 << 1
8
8
  const MODE_FLAG_JAPANESE_BASE = 1 << 2
9
9
  const MODE_FLAG_JAPANESE_PLUS = 1 << 3
10
10
  const MODE_FLAG_JAPANESE_ANY = MODE_FLAG_JAPANESE_BASE | MODE_FLAG_JAPANESE_PLUS
11
+ const HAS_OWN = Object.prototype.hasOwnProperty
11
12
  const REG_CJK_BREAKS_RULE_NAME = /(^|[_-])cjk_breaks([_-]|$)/
12
13
  const VALID_CANONICAL_MODES = new Set([
13
14
  'compatible',
@@ -126,27 +127,42 @@ const deriveOptionInfo = (opt) => {
126
127
  return opt
127
128
  }
128
129
 
129
- const getRuntimeOpt = (state, baseOpt) => {
130
- if (!state || !state.env || !state.env.__strongJaTokenOpt) return deriveOptionInfo(baseOpt)
131
- const override = state.env.__strongJaTokenOpt
132
- if (state.__strongJaTokenRuntimeOpt &&
133
- state.__strongJaTokenRuntimeBase === baseOpt &&
134
- state.__strongJaTokenRuntimeOverride === override) {
135
- return state.__strongJaTokenRuntimeOpt
136
- }
137
- const merged = { ...baseOpt, ...override }
138
- state.__strongJaTokenRuntimeOpt = deriveOptionInfo(merged)
139
- state.__strongJaTokenRuntimeBase = baseOpt
140
- state.__strongJaTokenRuntimeOverride = override
141
- return state.__strongJaTokenRuntimeOpt
142
- }
130
+ const hasRuntimeOverride = (override) => {
131
+ if (!override || typeof override !== 'object') return false
132
+ return (HAS_OWN.call(override, 'mode') && override.mode !== undefined) ||
133
+ (HAS_OWN.call(override, 'postprocess') && override.postprocess !== undefined)
134
+ }
135
+
136
+ const getRuntimeOpt = (state, baseOpt) => {
137
+ const override = state && state.env ? state.env.__strongJaTokenOpt : null
138
+ if (!hasRuntimeOverride(override)) return deriveOptionInfo(baseOpt)
139
+ if (state.__strongJaTokenRuntimeOpt &&
140
+ state.__strongJaTokenRuntimeBase === baseOpt &&
141
+ state.__strongJaTokenRuntimeOverride === override) {
142
+ return state.__strongJaTokenRuntimeOpt
143
+ }
144
+ const merged = baseOpt && typeof baseOpt === 'object' ? { ...baseOpt } : {}
145
+ if (HAS_OWN.call(override, 'mode') && override.mode !== undefined) merged.mode = override.mode
146
+ if (HAS_OWN.call(override, 'postprocess') && override.postprocess !== undefined) merged.postprocess = override.postprocess
147
+ state.__strongJaTokenRuntimeOpt = deriveOptionInfo(merged)
148
+ state.__strongJaTokenRuntimeBase = baseOpt
149
+ state.__strongJaTokenRuntimeOverride = override
150
+ return state.__strongJaTokenRuntimeOpt
151
+ }
143
152
 
144
153
  const getReferenceCount = (state) => {
145
154
  if (!state) return 0
146
155
  let referenceCount = state.__strongJaReferenceCount
147
156
  if (referenceCount !== undefined) return referenceCount
148
157
  const references = state.env && state.env.references
149
- referenceCount = references ? Object.keys(references).length : 0
158
+ if (!references) {
159
+ state.__strongJaReferenceCount = 0
160
+ return 0
161
+ }
162
+ referenceCount = 0
163
+ for (const key in references) {
164
+ if (HAS_OWN.call(references, key)) referenceCount++
165
+ }
150
166
  state.__strongJaReferenceCount = referenceCount
151
167
  return referenceCount
152
168
  }
@@ -223,9 +239,10 @@ export {
223
239
  isCjkBreaksRuleName,
224
240
  resolveMode,
225
241
  getModeFlags,
226
- deriveModeInfo,
227
- deriveOptionInfo,
228
- MODE_FLAG_COMPATIBLE,
242
+ deriveModeInfo,
243
+ deriveOptionInfo,
244
+ hasRuntimeOverride,
245
+ MODE_FLAG_COMPATIBLE,
229
246
  MODE_FLAG_AGGRESSIVE,
230
247
  MODE_FLAG_JAPANESE_BASE,
231
248
  MODE_FLAG_JAPANESE_PLUS,