@peaceroad/markdown-it-renderer-fence 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ const warnedOptionIssues = new Set()
2
+
3
+ const isWarnEnabled = () => {
4
+ if (typeof process === 'undefined' || !process || !process.env) return true
5
+ const mode = process.env.NODE_ENV
6
+ return mode === 'development'
7
+ }
8
+
9
+ const warnOptionIssueOnce = (code, message) => {
10
+ if (!isWarnEnabled()) return
11
+ const key = `${code}:${message}`
12
+ if (warnedOptionIssues.has(key)) return
13
+ warnedOptionIssues.add(key)
14
+ if (typeof console !== 'undefined' && console && typeof console.warn === 'function') {
15
+ console.warn(`[renderer-fence] ${message}`)
16
+ }
17
+ }
18
+
19
+ const validProviderSet = new Set(['shiki', 'hljs', 'custom'])
20
+ const validFallbackSet = new Set(['plain', 'markup'])
21
+ const validTransportSet = new Set(['env', 'inline-script'])
22
+ const validLineFeatureStrategySet = new Set(['hybrid', 'disable'])
23
+ const validShikiScopeModeSet = new Set(['auto', 'color', 'semantic', 'keyword'])
24
+ const knownCustomHighlightKeys = new Set([
25
+ 'provider',
26
+ 'getRanges',
27
+ 'fallback',
28
+ 'fallbackOn',
29
+ 'transport',
30
+ 'idPrefix',
31
+ 'lineFeatureStrategy',
32
+ 'scopePrefix',
33
+ 'includeScopeStyles',
34
+ 'shikiScopeMode',
35
+ 'shikiKeywordClassifier',
36
+ 'shikiKeywordLangResolver',
37
+ 'shikiKeywordLangAliases',
38
+ 'highlighter',
39
+ 'hljsHighlight',
40
+ 'highlight',
41
+ 'defaultLang',
42
+ 'theme',
43
+ ])
44
+
45
+ const validateCustomHighlightOptions = (rawOpt, normalizedOpt) => {
46
+ if (!rawOpt || typeof rawOpt !== 'object') return
47
+
48
+ for (const key of Object.keys(rawOpt)) {
49
+ if (!knownCustomHighlightKeys.has(key)) {
50
+ warnOptionIssueOnce(
51
+ 'unknown-option',
52
+ `Unknown customHighlight option "${key}" was ignored.`,
53
+ )
54
+ }
55
+ }
56
+
57
+ if (rawOpt.provider != null && !validProviderSet.has(normalizedOpt.provider)) {
58
+ warnOptionIssueOnce(
59
+ 'provider',
60
+ `Invalid customHighlight.provider "${String(rawOpt.provider)}". Using "${normalizedOpt.provider}".`,
61
+ )
62
+ }
63
+ if (rawOpt.fallback != null && !validFallbackSet.has(normalizedOpt.fallback)) {
64
+ warnOptionIssueOnce(
65
+ 'fallback',
66
+ `Invalid customHighlight.fallback "${String(rawOpt.fallback)}". Using "${normalizedOpt.fallback}".`,
67
+ )
68
+ }
69
+ if (rawOpt.transport != null && !validTransportSet.has(normalizedOpt.transport)) {
70
+ warnOptionIssueOnce(
71
+ 'transport',
72
+ `Invalid customHighlight.transport "${String(rawOpt.transport)}". Using "${normalizedOpt.transport}".`,
73
+ )
74
+ }
75
+ if (rawOpt.lineFeatureStrategy != null && !validLineFeatureStrategySet.has(normalizedOpt.lineFeatureStrategy)) {
76
+ warnOptionIssueOnce(
77
+ 'line-feature-strategy',
78
+ `Invalid customHighlight.lineFeatureStrategy "${String(rawOpt.lineFeatureStrategy)}". Using "${normalizedOpt.lineFeatureStrategy}".`,
79
+ )
80
+ }
81
+ if (rawOpt.shikiScopeMode != null && !validShikiScopeModeSet.has(normalizedOpt.shikiScopeMode)) {
82
+ warnOptionIssueOnce(
83
+ 'shiki-scope-mode',
84
+ `Invalid customHighlight.shikiScopeMode "${String(rawOpt.shikiScopeMode)}". Using "${normalizedOpt.shikiScopeMode}".`,
85
+ )
86
+ }
87
+ if (rawOpt.fallbackOn != null && !Array.isArray(rawOpt.fallbackOn)) {
88
+ warnOptionIssueOnce(
89
+ 'fallback-on',
90
+ 'customHighlight.fallbackOn should be an array; using default fallback reasons.',
91
+ )
92
+ }
93
+
94
+ if (rawOpt.theme != null) {
95
+ if (typeof rawOpt.theme !== 'string' && (typeof rawOpt.theme !== 'object' || Array.isArray(rawOpt.theme))) {
96
+ warnOptionIssueOnce(
97
+ 'theme-type',
98
+ 'customHighlight.theme should be a string or an object { light, dark, default? }.',
99
+ )
100
+ } else if (rawOpt.theme && typeof rawOpt.theme === 'object' && !Array.isArray(rawOpt.theme)) {
101
+ const hasLight = (typeof rawOpt.theme.light === 'string' && rawOpt.theme.light.trim().length > 0)
102
+ const hasDark = (typeof rawOpt.theme.dark === 'string' && rawOpt.theme.dark.trim().length > 0)
103
+ if (rawOpt.theme.light != null && typeof rawOpt.theme.light !== 'string') {
104
+ warnOptionIssueOnce(
105
+ 'theme-light-type',
106
+ 'customHighlight.theme.light should be a non-empty string when provided.',
107
+ )
108
+ }
109
+ if (rawOpt.theme.dark != null && typeof rawOpt.theme.dark !== 'string') {
110
+ warnOptionIssueOnce(
111
+ 'theme-dark-type',
112
+ 'customHighlight.theme.dark should be a non-empty string when provided.',
113
+ )
114
+ }
115
+ if (!hasLight && !hasDark) {
116
+ warnOptionIssueOnce(
117
+ 'theme-missing-variants',
118
+ 'customHighlight.theme object should include at least one of { light, dark }.',
119
+ )
120
+ }
121
+ if (rawOpt.theme.default != null) {
122
+ const key = String(rawOpt.theme.default).trim().toLowerCase()
123
+ if (key !== 'light' && key !== 'dark') {
124
+ warnOptionIssueOnce(
125
+ 'theme-default',
126
+ 'customHighlight.theme.default should be "light" or "dark".',
127
+ )
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ if (normalizedOpt.provider === 'custom' && typeof normalizedOpt.getRanges !== 'function') {
134
+ warnOptionIssueOnce(
135
+ 'custom-provider-ranges',
136
+ 'customHighlight.provider="custom" requires customHighlight.getRanges(code, lang, context).',
137
+ )
138
+ }
139
+ if (normalizedOpt.provider === 'shiki') {
140
+ const ok = !!(normalizedOpt.highlighter && typeof normalizedOpt.highlighter.codeToTokens === 'function')
141
+ if (!ok) {
142
+ warnOptionIssueOnce(
143
+ 'shiki-provider-highlighter',
144
+ 'customHighlight.provider="shiki" requires customHighlight.highlighter.codeToTokens().',
145
+ )
146
+ }
147
+ }
148
+ }
149
+
150
+ export {
151
+ validateCustomHighlightOptions,
152
+ }
@@ -0,0 +1,43 @@
1
+ const escapeJsonForScript = (data) => {
2
+ return JSON.stringify(data)
3
+ .replace(/</g, '\\u003C')
4
+ .replace(/>/g, '\\u003E')
5
+ .replace(/&/g, '\\u0026')
6
+ .replace(/\u2028/g, '\\u2028')
7
+ .replace(/\u2029/g, '\\u2029')
8
+ }
9
+
10
+ const escapeHtmlAttr = (value) => {
11
+ return String(value)
12
+ .replace(/&/g, '&amp;')
13
+ .replace(/"/g, '&quot;')
14
+ .replace(/'/g, '&#39;')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;')
17
+ }
18
+
19
+ const getCustomHighlightPayloadMap = (env, envKey = 'rendererFenceCustomHighlights') => {
20
+ if (!env || typeof env !== 'object') return {}
21
+ const map = env[envKey]
22
+ if (!map || typeof map !== 'object') return {}
23
+ return map
24
+ }
25
+
26
+ const renderCustomHighlightPayloadScript = (
27
+ env,
28
+ scriptId = 'pre-highlight-data',
29
+ envKey = 'rendererFenceCustomHighlights',
30
+ ) => {
31
+ const map = getCustomHighlightPayloadMap(env, envKey)
32
+ const keys = Object.keys(map)
33
+ if (!keys.length) return ''
34
+ const id = escapeHtmlAttr(scriptId || 'pre-highlight-data')
35
+ return `<script type="application/json" id="${id}">${escapeJsonForScript(map)}</script>`
36
+ }
37
+
38
+ export {
39
+ escapeHtmlAttr,
40
+ escapeJsonForScript,
41
+ getCustomHighlightPayloadMap,
42
+ renderCustomHighlightPayloadScript,
43
+ }
@@ -0,0 +1,83 @@
1
+ const styleValueSafeReg = /^[#(),.%\w\s-]+$/
2
+
3
+ const getPayloadMapFromScript = (root, scriptId = 'pre-highlight-data', inlineAttrName = 'data-pre-highlight') => {
4
+ if (!root || typeof root.querySelector !== 'function') return {}
5
+ const map = {}
6
+ const safeScriptId = scriptId || 'pre-highlight-data'
7
+ const script = root.querySelector(`#${safeScriptId}`)
8
+ if (script && script.textContent) {
9
+ try {
10
+ const parsed = JSON.parse(script.textContent)
11
+ if (parsed && typeof parsed === 'object') Object.assign(map, parsed)
12
+ } catch (e) {}
13
+ }
14
+ const inlineScripts = root.querySelectorAll(`script[type="application/json"][${inlineAttrName}]`)
15
+ for (const item of inlineScripts) {
16
+ const id = item && typeof item.getAttribute === 'function' ? item.getAttribute(inlineAttrName) : null
17
+ if (!id || !item.textContent) continue
18
+ try {
19
+ map[id] = JSON.parse(item.textContent)
20
+ } catch (e) {}
21
+ }
22
+ return map
23
+ }
24
+
25
+ const collectTextSegments = (node) => {
26
+ const segments = []
27
+ const doc = node && (node.ownerDocument || (typeof document !== 'undefined' ? document : null))
28
+ if (!doc || typeof doc.createTreeWalker !== 'function') return segments
29
+ const textMask = typeof NodeFilter !== 'undefined' ? NodeFilter.SHOW_TEXT : 4
30
+ const walker = doc.createTreeWalker(node, textMask)
31
+ let current = walker.nextNode()
32
+ let cursor = 0
33
+ while (current) {
34
+ const len = current.nodeValue ? current.nodeValue.length : 0
35
+ segments.push({ node: current, start: cursor, end: cursor + len })
36
+ cursor += len
37
+ current = walker.nextNode()
38
+ }
39
+ return segments
40
+ }
41
+
42
+ const findTextPosition = (segments, offset) => {
43
+ let lo = 0
44
+ let hi = segments.length - 1
45
+ while (lo <= hi) {
46
+ const mid = (lo + hi) >> 1
47
+ const seg = segments[mid]
48
+ if (offset < seg.start) {
49
+ hi = mid - 1
50
+ continue
51
+ }
52
+ if (offset > seg.end) {
53
+ lo = mid + 1
54
+ continue
55
+ }
56
+ return { node: seg.node, offset: offset - seg.start }
57
+ }
58
+ if (segments.length === 0) return null
59
+ const last = segments[segments.length - 1]
60
+ if (offset === last.end) return { node: last.node, offset: last.end - last.start }
61
+ return null
62
+ }
63
+
64
+ const styleValueSafe = (val) => {
65
+ return typeof val === 'string' && styleValueSafeReg.test(val)
66
+ }
67
+
68
+ const styleToHighlightCss = (style) => {
69
+ if (!style || typeof style !== 'object') return ''
70
+ const parts = []
71
+ if (styleValueSafe(style.color)) parts.push(`color:${style.color}`)
72
+ if (styleValueSafe(style.backgroundColor)) parts.push(`background-color:${style.backgroundColor}`)
73
+ if (styleValueSafe(style.textDecoration)) parts.push(`text-decoration:${style.textDecoration}`)
74
+ if (styleValueSafe(style.textShadow)) parts.push(`text-shadow:${style.textShadow}`)
75
+ return parts.join(';')
76
+ }
77
+
78
+ export {
79
+ collectTextSegments,
80
+ findTextPosition,
81
+ getPayloadMapFromScript,
82
+ styleToHighlightCss,
83
+ }
@@ -0,0 +1,407 @@
1
+ const shikiSimpleIdentifierReg = /^[A-Za-z_$][\w$]*$/
2
+ const shikiPunctuationTokenReg = /^[()[\]{}<>.,;:!?~`'"@#$%^&*+=|/\\-]+$/
3
+ const shikiFunctionLikeIdentifierReg = /^[A-Za-z_$][\w$]*[!?]?$/
4
+ const shikiShellOptionTokenReg = /^-[A-Za-z0-9][A-Za-z0-9-]*$/
5
+ const shikiShellTestOperatorSet = new Set(['-n', '-z', '-gt', '-lt', '-ge', '-le', '-eq', '-ne'])
6
+
7
+ const keywordV4GlobalRules = [
8
+ {
9
+ id: 'css-color-name-to-number',
10
+ priority: 2100,
11
+ scopeIncludesAny: ['support.constant.color.w3c-standard-color-name.css'],
12
+ setBucket: 'number',
13
+ },
14
+ {
15
+ id: 'rust-lifetime-to-type-name',
16
+ priority: 2050,
17
+ lang: ['rust'],
18
+ scopeIncludesAny: ['entity.name.type.lifetime.rust'],
19
+ setBucket: 'type-name',
20
+ },
21
+ {
22
+ id: 'regex-alternative-operator-keyword',
23
+ priority: 2040,
24
+ scopeIncludesAny: ['keyword.operator.or.regexp'],
25
+ tokenEquals: ['|'],
26
+ setBucket: 'keyword',
27
+ },
28
+ ]
29
+
30
+ const keywordV4LangRules = {
31
+ sql: [
32
+ {
33
+ id: 'sql-window-function-blue',
34
+ priority: 2000,
35
+ scopeIncludesAny: ['support.function.ranking.sql'],
36
+ setBucket: 'type',
37
+ },
38
+ {
39
+ id: 'sql-table-database-name-blue',
40
+ priority: 1980,
41
+ scopeIncludesAny: ['constant.other.table-name.sql', 'constant.other.database-name.sql'],
42
+ setBucket: 'type',
43
+ },
44
+ ],
45
+ java: [
46
+ {
47
+ id: 'java-record-signature-neutral',
48
+ priority: 1995,
49
+ scopeIncludesAny: ['meta.record.identifier.java'],
50
+ tokenRegex: /[(),]/,
51
+ setBucket: 'text',
52
+ },
53
+ {
54
+ id: 'java-package-separator-neutral',
55
+ priority: 1980,
56
+ scopeIncludesAny: ['punctuation.separator.java'],
57
+ setBucket: 'text',
58
+ },
59
+ {
60
+ id: 'java-generic-bracket-neutral',
61
+ priority: 1970,
62
+ scopeIncludesAny: ['punctuation.bracket.angle.java'],
63
+ setBucket: 'text',
64
+ },
65
+ {
66
+ id: 'java-record-keyword',
67
+ priority: 1960,
68
+ scopeIncludesAny: ['storage.modifier.java'],
69
+ tokenKind: 'identifier',
70
+ tokenEquals: ['record'],
71
+ setBucket: 'keyword',
72
+ },
73
+ {
74
+ id: 'java-record-type-name',
75
+ priority: 1950,
76
+ scopeIncludesAny: ['entity.name.type.record.java'],
77
+ tokenKind: 'identifier',
78
+ setBucket: 'type-name',
79
+ },
80
+ {
81
+ id: 'java-primitive-keyword',
82
+ priority: 1940,
83
+ scopeIncludesAny: ['storage.type.primitive.java'],
84
+ tokenKind: 'identifier',
85
+ setBucket: 'keyword',
86
+ },
87
+ ],
88
+ php: [
89
+ {
90
+ id: 'php-array-func-builtin',
91
+ priority: 2000,
92
+ scopeIncludesAny: ['support.function.array.php'],
93
+ setBucket: 'title-function-builtin',
94
+ },
95
+ {
96
+ id: 'php-core-const-blue',
97
+ priority: 1980,
98
+ scopeIncludesAny: ['support.constant.core.php', 'constant.other.php'],
99
+ setBucket: 'variable-const',
100
+ },
101
+ {
102
+ id: 'php-enum-const-blue',
103
+ priority: 1970,
104
+ scopeIncludesAny: ['constant.enum.php', 'constant.other.class.php'],
105
+ setBucket: 'variable-const',
106
+ },
107
+ ],
108
+ ruby: [
109
+ {
110
+ id: 'ruby-entity-function-title',
111
+ priority: 2000,
112
+ scopeIncludesAny: ['entity.name.function.ruby'],
113
+ setBucket: 'title-function',
114
+ },
115
+ {
116
+ id: 'ruby-variable-accent',
117
+ priority: 1980,
118
+ scopeIncludesAny: ['variable.ruby'],
119
+ baseIn: ['variable-member'],
120
+ tokenKind: 'identifier',
121
+ setBucket: 'variable',
122
+ },
123
+ ],
124
+ typescript: [
125
+ {
126
+ id: 'ts-support-property-blue',
127
+ priority: 1950,
128
+ scopeIncludesAny: ['support.variable.property.ts'],
129
+ setBucket: 'variable-const',
130
+ },
131
+ ],
132
+ javascript: [
133
+ {
134
+ id: 'js-support-class-blue',
135
+ priority: 1960,
136
+ scopeIncludesAny: ['support.class.'],
137
+ setBucket: 'type',
138
+ },
139
+ ],
140
+ go: [
141
+ {
142
+ id: 'go-storage-type-keyword',
143
+ priority: 1960,
144
+ scopeIncludesAny: ['storage.type.string.go', 'storage.type.boolean.go'],
145
+ setBucket: 'keyword',
146
+ },
147
+ {
148
+ id: 'go-format-placeholder-blue',
149
+ priority: 1940,
150
+ scopeIncludesAny: ['constant.other.placeholder.go'],
151
+ setBucket: 'literal',
152
+ },
153
+ ],
154
+ csharp: [
155
+ {
156
+ id: 'csharp-keyword-type-keyword',
157
+ priority: 1960,
158
+ scopeIncludesAny: ['keyword.type.string.cs'],
159
+ setBucket: 'keyword',
160
+ },
161
+ ],
162
+ c: [
163
+ {
164
+ id: 'c-array-bracket-keyword',
165
+ priority: 1960,
166
+ scopeIncludesAny: ['storage.modifier.array.bracket.square.c', 'storage-modifier-array-bracket-square-c'],
167
+ setBucket: 'keyword',
168
+ },
169
+ {
170
+ id: 'c-array-bracket-token-keyword',
171
+ priority: 1955,
172
+ baseIn: ['type'],
173
+ tokenEquals: ['[]'],
174
+ setBucket: 'keyword',
175
+ },
176
+ {
177
+ id: 'c-placeholder-blue',
178
+ priority: 1950,
179
+ scopeIncludesAny: ['constant.other.placeholder.c'],
180
+ setBucket: 'literal',
181
+ },
182
+ ],
183
+ cpp: [
184
+ {
185
+ id: 'cpp-reference-modifier-keyword',
186
+ priority: 1960,
187
+ scopeIncludesAny: ['storage.modifier.reference.cpp'],
188
+ setBucket: 'keyword',
189
+ },
190
+ ],
191
+ python: [
192
+ {
193
+ id: 'python-fstring-conversion-neutral',
194
+ priority: 1900,
195
+ baseIn: ['string'],
196
+ scopeIncludesAny: ['meta.fstring.python'],
197
+ tokenEquals: ['s', 'r', 'a'],
198
+ setBucket: 'text',
199
+ },
200
+ {
201
+ id: 'python-fstring-identifier-neutral',
202
+ priority: 1890,
203
+ baseIn: ['string'],
204
+ scopeIncludesAny: ['meta.fstring.python'],
205
+ tokenKind: 'identifier',
206
+ setBucket: 'text',
207
+ },
208
+ ],
209
+ css: [
210
+ {
211
+ id: 'css-function-type-blue',
212
+ priority: 1940,
213
+ scopeIncludesAny: ['support.function.calc.css', 'support.function.gradient.css'],
214
+ setBucket: 'type',
215
+ },
216
+ ],
217
+ }
218
+
219
+ const keywordV4SortedGlobalRules = keywordV4GlobalRules.slice().sort((a, b) => (b.priority || 0) - (a.priority || 0))
220
+ const keywordV4SortedLangRules = new Map(
221
+ Object.keys(keywordV4LangRules).map((lang) => [
222
+ lang,
223
+ keywordV4LangRules[lang].slice().sort((a, b) => (b.priority || 0) - (a.priority || 0)),
224
+ ]),
225
+ )
226
+
227
+ const applyShikiKeywordLegacyPostRules = (currentBucket, ctx, helper) => {
228
+ const postHelper = helper && typeof helper === 'object' ? helper : {}
229
+ const hasAnyScopePattern = typeof postHelper.hasAnyScopePattern === 'function'
230
+ ? postHelper.hasAnyScopePattern
231
+ : () => false
232
+ const hasScopePattern = typeof postHelper.hasScopePattern === 'function'
233
+ ? postHelper.hasScopePattern
234
+ : (pattern) => hasAnyScopePattern([pattern])
235
+ const classifyToken = typeof postHelper.classifyToken === 'function'
236
+ ? postHelper.classifyToken
237
+ : () => null
238
+
239
+ const bucket = String(currentBucket || 'text')
240
+ const langKey = String(ctx && ctx.langKey || '')
241
+ const lowerScopeCandidates = Array.isArray(ctx && ctx.lowerScopeCandidates) ? ctx.lowerScopeCandidates : []
242
+ const tokenContent = String(ctx && ctx.tokenContent || '')
243
+ const trimmed = String(ctx && ctx.tokenTrim || '')
244
+ const tokenLower = (ctx && typeof ctx.tokenLower === 'string') ? ctx.tokenLower : trimmed.toLowerCase()
245
+ if (!trimmed) return 'text'
246
+
247
+ if ((langKey === 'bash' || langKey === 'shellscript') && shikiShellOptionTokenReg.test(trimmed)) {
248
+ return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
249
+ }
250
+ if ((langKey === 'hcl' || langKey === 'terraform') && trimmed === '=') return 'keyword'
251
+ if (bucket === 'comment' && trimmed.startsWith('#!')) return 'meta-shebang'
252
+ if (bucket === 'string') {
253
+ if (
254
+ (langKey === 'bash' || langKey === 'shellscript') &&
255
+ hasAnyScopePattern(['entity.name.function.shell', 'entity.name.command.shell'])
256
+ ) return 'title-function'
257
+ if (
258
+ (langKey === 'bash' || langKey === 'shellscript') &&
259
+ (
260
+ hasAnyScopePattern(['variable.parameter.positional.shell', 'variable.language.special.shell']) ||
261
+ shikiShellOptionTokenReg.test(trimmed) ||
262
+ trimmed.startsWith('${') ||
263
+ trimmed === '}'
264
+ )
265
+ ) return 'literal'
266
+ if (
267
+ (langKey === 'bash' || langKey === 'shellscript') &&
268
+ hasAnyScopePattern(['constant.other.option'])
269
+ ) return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
270
+ if (langKey === 'python' && hasAnyScopePattern(['storage.type.string.python'])) return 'keyword'
271
+ if (langKey === 'go' && hasAnyScopePattern(['entity.name.import.go'])) return 'type-name'
272
+ if (hasAnyScopePattern(['keyword.operator.string'])) return 'keyword'
273
+ if (hasAnyScopePattern(['constant.character.escape', 'constant.character.format.placeholder'])) return 'number'
274
+ if (hasAnyScopePattern(['variable.other.', 'variable.ruby', 'meta.attribute.python'])) return 'variable-plain'
275
+ }
276
+ if (bucket === 'string-unquoted') {
277
+ if (
278
+ (langKey === 'bash' || langKey === 'shellscript') &&
279
+ hasAnyScopePattern(['entity.name.function.shell', 'entity.name.command.shell'])
280
+ ) return 'title-function'
281
+ if (
282
+ (langKey === 'bash' || langKey === 'shellscript') &&
283
+ (
284
+ hasAnyScopePattern(['variable.parameter.positional.shell', 'variable.language.special.shell']) ||
285
+ shikiShellOptionTokenReg.test(trimmed) ||
286
+ trimmed.startsWith('${') ||
287
+ trimmed === '}'
288
+ )
289
+ ) return 'literal'
290
+ if (
291
+ (langKey === 'bash' || langKey === 'shellscript') &&
292
+ hasAnyScopePattern(['constant.other.option'])
293
+ ) return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
294
+ if (hasAnyScopePattern(['entity.name.tag.yaml'])) return 'tag'
295
+ return 'string'
296
+ }
297
+ if (bucket === 'title-function') {
298
+ if (langKey === 'css' && hasAnyScopePattern(['support.function.misc.css'])) return 'type'
299
+ if (hasAnyScopePattern(['support.function.builtin', 'support.function.kernel', 'support.function.construct'])) return 'title-function-builtin'
300
+ if (hasAnyScopePattern(['storage.type.function.arrow', 'keyword.operator'])) return 'keyword'
301
+ if (hasAnyScopePattern(['punctuation.section.function', 'punctuation.definition.arguments', 'meta.function-call.arguments'])) return 'punctuation'
302
+ if (hasAnyScopePattern(['meta.function']) && !hasAnyScopePattern(['entity.name.function', 'support.function'])) return 'meta'
303
+ if (hasAnyScopePattern(['punctuation.accessor'])) return 'text'
304
+ if (!shikiFunctionLikeIdentifierReg.test(trimmed)) {
305
+ if (shikiPunctuationTokenReg.test(trimmed)) return 'punctuation'
306
+ if (/[.\[\](){}:]/.test(trimmed)) return 'text'
307
+ }
308
+ }
309
+ if (bucket === 'title-function-builtin' && hasAnyScopePattern(['entity.name.function.macro'])) return 'title-function'
310
+ if (bucket === 'type' && langKey === 'csharp' && hasScopePattern('keyword.type.')) return 'keyword'
311
+ if (bucket === 'type' && langKey === 'rust' && hasAnyScopePattern(['entity.name.type.numeric.rust'])) return 'type-name'
312
+ if (bucket === 'type-name' && langKey === 'php' && hasScopePattern('support.class.php')) return 'type'
313
+ if (bucket === 'type-name' && langKey === 'ruby' && hasScopePattern('support.class.ruby')) return 'type'
314
+ if (bucket === 'meta') {
315
+ if (trimmed.startsWith('#!') || hasAnyScopePattern(['meta.shebang'])) return 'meta-shebang'
316
+ if (hasAnyScopePattern(['entity.name.scope-resolution', 'entity.name.namespace'])) return 'namespace'
317
+ if (hasAnyScopePattern(['source.sql', 'source.python', 'source.ruby', 'source.rust'])) return 'text'
318
+ if (hasAnyScopePattern(['punctuation.terminator.statement', 'punctuation.section.block', 'punctuation.brackets.curly', 'meta.body.function.definition'])) return 'punctuation'
319
+ }
320
+ if (bucket === 'variable') {
321
+ if (
322
+ hasAnyScopePattern(['meta.type.annotation']) &&
323
+ shikiPunctuationTokenReg.test(trimmed)
324
+ ) return 'punctuation'
325
+ if (hasAnyScopePattern(['variable.language.this'])) return 'variable-this'
326
+ if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
327
+ if (hasAnyScopePattern(['variable.object.property', 'variable.other.property', 'variable.other.member', 'variable.object.c', 'variable.ruby'])) return 'variable-member'
328
+ if (!hasAnyScopePattern(['variable.', 'entity.name.variable']) && hasAnyScopePattern(['source.'])) return 'text'
329
+ if (hasAnyScopePattern(['variable.other.readwrite', 'variable.other.normal', 'variable.other.php', 'entity.name.variable.local'])) return 'variable-plain'
330
+ }
331
+ if (bucket === 'variable-plain') {
332
+ if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
333
+ if (hasAnyScopePattern(['entity.name.variable.local.cs'])) return 'type-name'
334
+ if (hasAnyScopePattern(['source.sql', 'source.python', 'source.ruby'])) return 'text'
335
+ if (!shikiSimpleIdentifierReg.test(trimmed) && hasAnyScopePattern(['punctuation.'])) return 'punctuation'
336
+ }
337
+ if (bucket === 'variable-member') {
338
+ if (hasAnyScopePattern(['variable.object.property.ts', 'variable-object-property-ts', 'variable.object.property.js', 'variable-object-property-js'])) return 'variable-property'
339
+ if (hasAnyScopePattern(['variable.object.c'])) return 'variable-parameter'
340
+ }
341
+ if (bucket === 'variable-parameter') {
342
+ if (langKey === 'csharp' && hasAnyScopePattern(['entity.name.variable.parameter.cs'])) return 'type-name'
343
+ if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
344
+ if (hasAnyScopePattern(['variable.object.property', 'variable.other.property', 'variable.other.member', 'variable.object.c', 'variable.ruby'])) return 'variable-member'
345
+ if (hasAnyScopePattern(['variable.other.readwrite.hcl', 'variable.other.normal.shell', 'meta.block.hcl'])) return 'variable-plain'
346
+ if (hasAnyScopePattern(['storage.type.built-in.primitive', 'keyword.operator.type.annotation', 'keyword.operator.type'])) return 'keyword'
347
+ if (hasAnyScopePattern(['entity.name.type', 'support.type'])) return 'type-name'
348
+ if (!shikiSimpleIdentifierReg.test(trimmed)) return 'punctuation'
349
+ }
350
+ if (bucket === 'variable-property') {
351
+ if (hasAnyScopePattern(['variable.object.property', 'variable.other.property'])) return 'variable-parameter'
352
+ }
353
+ if (bucket === 'keyword' && langKey === 'php' && hasAnyScopePattern(['support.function.construct.output.php'])) return 'title-function-builtin'
354
+ if (bucket === 'keyword' && hasAnyScopePattern(['variable.language.this'])) return 'variable-this'
355
+ if (bucket === 'literal' && langKey === 'sql') return 'text'
356
+ if (bucket === 'text') {
357
+ const tokenBucket = classifyToken(tokenContent, langKey)
358
+ if (tokenBucket) return tokenBucket
359
+ if (shikiSimpleIdentifierReg.test(trimmed)) return 'variable-plain'
360
+ }
361
+ if (!shikiSimpleIdentifierReg.test(trimmed) && (bucket === 'text' || bucket === 'meta')) {
362
+ const tokenBucket = classifyToken(tokenContent, langKey)
363
+ if (tokenBucket) return tokenBucket
364
+ }
365
+ return bucket
366
+ }
367
+
368
+ const matchKeywordV4Rule = (rule, currentBucket, ctx) => {
369
+ const hasAnyScopePattern = (ctx && typeof ctx.hasAnyScopePattern === 'function')
370
+ ? ctx.hasAnyScopePattern
371
+ : (() => false)
372
+ if (!rule) return false
373
+ if (Array.isArray(rule.baseIn) && rule.baseIn.length > 0 && !rule.baseIn.includes(currentBucket)) return false
374
+ if (Array.isArray(rule.lang) && rule.lang.length > 0 && !rule.lang.includes(ctx.langKey)) return false
375
+ if (Array.isArray(rule.scopeIncludesAny) && rule.scopeIncludesAny.length > 0 && !hasAnyScopePattern(rule.scopeIncludesAny)) return false
376
+ if (Array.isArray(rule.scopeExcludesAny) && rule.scopeExcludesAny.length > 0 && hasAnyScopePattern(rule.scopeExcludesAny)) return false
377
+ if (Array.isArray(rule.tokenEquals) && rule.tokenEquals.length > 0 && !rule.tokenEquals.includes(ctx.tokenLower)) return false
378
+ if (rule.tokenRegex instanceof RegExp && !rule.tokenRegex.test(ctx.tokenTrim)) return false
379
+ if (rule.tokenKind === 'identifier' && !ctx.isIdentifier) return false
380
+ if (rule.tokenKind === 'punctuation' && !ctx.isPunctuation) return false
381
+ return true
382
+ }
383
+
384
+ const applyRuleSet = (currentBucket, rules, ctx) => {
385
+ if (!Array.isArray(rules) || rules.length === 0) return currentBucket
386
+ let next = currentBucket
387
+ for (let i = 0; i < rules.length; i++) {
388
+ const rule = rules[i]
389
+ if (!matchKeywordV4Rule(rule, next, ctx)) continue
390
+ next = rule.setBucket || next
391
+ if (rule.stop !== false) break
392
+ }
393
+ return next
394
+ }
395
+
396
+ const applyKeywordV4Rules = (currentBucket, ctx) => {
397
+ let next = applyRuleSet(currentBucket, keywordV4SortedGlobalRules, ctx)
398
+ const langRules = keywordV4SortedLangRules.get(ctx.langKey)
399
+ next = applyRuleSet(next, langRules, ctx)
400
+ return next
401
+ }
402
+
403
+ export {
404
+ applyKeywordV4Rules,
405
+ applyShikiKeywordLegacyPostRules,
406
+ }
407
+