sugar-high 2.5.0 → 2.5.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.
package/README.md CHANGED
@@ -31,28 +31,6 @@ import { highlight } from 'sugar-high'
31
31
  const html = highlight('const ready = true')
32
32
  ```
33
33
 
34
- ## Experimental WebGPU highlighting
35
-
36
- Use the opt-in `sugar-high/gpu` entry for asynchronous, language-agnostic highlighting powered by
37
- [`gpu-lexer`](https://gpu-lexer.vercel.app). It requires WebGPU and rejects when WebGPU is
38
- unavailable; importing any other Sugar High entry does not load the GPU model.
39
-
40
- Install the experimental lexer separately when using this entry:
41
-
42
- ```sh
43
- npm install sugar-high gpu-lexer
44
- ```
45
-
46
- ```js
47
- import { highlight, parse } from 'sugar-high/gpu'
48
-
49
- const parsed = await parse(source)
50
- const html = await highlight(source)
51
- ```
52
-
53
- GPU labels map onto Sugar High's existing token classes, themes, `cx`, `mark`, and `markLine`
54
- hooks. The model infers syntax without a language option and may differ from grammar-based output.
55
-
56
34
  JavaScript, including JSX, is the default. Pass a canonical name for another built-in language:
57
35
 
58
36
  ```js
@@ -241,6 +219,28 @@ See [`docs/API.md`](https://github.com/huozhi/sugar-high/blob/main/docs/API.md)
241
219
  options, and lower-level functions. Upgrading from v1? Read the
242
220
  [v2 migration guide](https://github.com/huozhi/sugar-high/blob/main/docs/MIGRATION.md).
243
221
 
222
+ ## Experimental WebGPU highlighting
223
+
224
+ Use the opt-in `sugar-high/gpu` entry for asynchronous, language-agnostic highlighting powered by
225
+ [`gpu-lexer`](https://gpu-lexer.vercel.app). It requires WebGPU and rejects when WebGPU is
226
+ unavailable; importing any other Sugar High entry does not load the GPU model.
227
+
228
+ Install the experimental lexer separately when using this entry:
229
+
230
+ ```sh
231
+ npm install sugar-high gpu-lexer
232
+ ```
233
+
234
+ ```js
235
+ import { highlight, parse } from 'sugar-high/gpu'
236
+
237
+ const parsed = await parse(source)
238
+ const html = await highlight(source)
239
+ ```
240
+
241
+ GPU labels map onto Sugar High's existing token classes, themes, `cx`, `mark`, and `markLine`
242
+ hooks. The model infers syntax without a language option and may differ from grammar-based output.
243
+
244
244
  ## Benchmarks
245
245
 
246
246
  Sugar High, PrismJS, and highlight.js highlighting the same generated TypeScript files:
package/lib/lang/css.js CHANGED
@@ -36,17 +36,23 @@ const isHyphen = ([type, value]) => type === T_SIGN && value === '-'
36
36
  * @param {Array<[number, string]>} tokens
37
37
  */
38
38
  const mergeDashedNames = (tokens) => {
39
+ /** @type {Array<[number, string]>} */
40
+ const merged = []
39
41
  for (let index = 0; index < tokens.length; index++) {
40
42
  let firstWord = index
41
43
  let end = index
42
44
 
43
45
  if (isHyphen(tokens[end])) {
44
46
  while (tokens[end] && isHyphen(tokens[end])) end++
45
- if (!tokens[end] || !isNameStart(tokens[end])) continue
47
+ if (!tokens[end] || !isNameStart(tokens[end])) {
48
+ merged.push(tokens[index])
49
+ continue
50
+ }
46
51
  firstWord = end++
47
52
  } else if (isNameStart(tokens[end])) {
48
53
  end++
49
54
  } else {
55
+ merged.push(tokens[index])
50
56
  continue
51
57
  }
52
58
 
@@ -62,10 +68,15 @@ const mergeDashedNames = (tokens) => {
62
68
  end++
63
69
  }
64
70
 
65
- if (!dashed) continue
71
+ if (!dashed) {
72
+ merged.push(tokens[index])
73
+ continue
74
+ }
66
75
  const name = tokens.slice(index, end).map(([, value]) => value).join('')
67
- tokens.splice(index, end - index, [tokens[firstWord][0], name])
76
+ merged.push([tokens[firstWord][0], name])
77
+ index = end - 1
68
78
  }
79
+ return merged
69
80
  }
70
81
 
71
82
  /** Return true when a colon belongs to a nested selector instead of a declaration. */
@@ -91,29 +102,37 @@ const opensBlock = (tokens, start) => {
91
102
  * @param {import('../core.js').ParseOptions} options
92
103
  */
93
104
  export const tokenize = (code, options) => {
94
- const tokens = tokenizePlain(code, { ...options, tokenize: undefined })
95
- mergeDashedNames(tokens)
105
+ const tokens = mergeDashedNames(tokenizePlain(code, { ...options, tokenize: undefined }))
106
+ /** @type {Array<[number, string]>} */
107
+ const output = []
96
108
  let blockDepth = 0
97
109
  let declarationStart = false
98
110
 
99
111
  for (let index = 0; index < tokens.length; index++) {
100
- const [type, value] = tokens[index]
112
+ const token = tokens[index]
113
+ const [type, value] = token
101
114
 
102
115
  if (type === T_SIGN && value === '{') {
103
116
  blockDepth++
104
117
  declarationStart = true
118
+ output.push(token)
105
119
  continue
106
120
  }
107
121
  if (type === T_SIGN && value === '}') {
108
122
  blockDepth--
109
123
  declarationStart = false
124
+ output.push(token)
110
125
  continue
111
126
  }
112
127
  if (type === T_SIGN && value === ';') {
113
128
  declarationStart = blockDepth > 0
129
+ output.push(token)
130
+ continue
131
+ }
132
+ if (!declarationStart || isIgnored(type)) {
133
+ output.push(token)
114
134
  continue
115
135
  }
116
- if (!declarationStart || isIgnored(type)) continue
117
136
 
118
137
  const propertyStart = index
119
138
  let propertyEnd = index
@@ -133,10 +152,13 @@ export const tokenize = (code, options) => {
133
152
  .slice(propertyStart, propertyEnd)
134
153
  .map(([, part]) => part)
135
154
  .join('')
136
- tokens.splice(propertyStart, propertyEnd - propertyStart, [T_PROPERTY, property])
155
+ output.push([T_PROPERTY, property])
156
+ index = propertyEnd - 1
157
+ } else {
158
+ output.push(token)
137
159
  }
138
160
  declarationStart = false
139
161
  }
140
162
 
141
- return tokens
163
+ return output
142
164
  }
@@ -35,6 +35,8 @@ function findTagEnd(code, start) {
35
35
  /** Find real script/style blocks while ignoring comments and quoted attributes. */
36
36
  function findEmbeddedBlocks(code) {
37
37
  const blocks = []
38
+ /** @type {Set<string>} */
39
+ const unclosed = new Set()
38
40
  let index = 0
39
41
  while (index < code.length) {
40
42
  if (code.startsWith('<!--', index)) {
@@ -57,10 +59,15 @@ function findEmbeddedBlocks(code) {
57
59
  }
58
60
 
59
61
  const tag = match[1].toLowerCase()
62
+ if (unclosed.has(tag)) {
63
+ index = openEnd
64
+ continue
65
+ }
60
66
  const closing = embeddedClosingTag(tag)
61
67
  closing.lastIndex = openEnd
62
68
  const closeMatch = closing.exec(code)
63
69
  if (!closeMatch) {
70
+ unclosed.add(tag)
64
71
  index = openEnd
65
72
  continue
66
73
  }
@@ -77,22 +84,29 @@ function findEmbeddedBlocks(code) {
77
84
  * @returns {Array<[number, string]>}
78
85
  */
79
86
  export function tokenizeEmbeddedHtml(code) {
87
+ const blocks = findEmbeddedBlocks(code)
88
+ if (!blocks.length) return tokenizeHtml(code)
89
+
80
90
  /** @type {Array<[number, string]>} */
81
91
  const tokens = []
92
+ /** @param {Array<[number, string]>} part */
93
+ const append = (part) => {
94
+ for (let index = 0; index < part.length; index++) tokens.push(part[index])
95
+ }
82
96
  let cursor = 0
83
97
 
84
- for (const block of findEmbeddedBlocks(code)) {
98
+ for (const block of blocks) {
85
99
  const { start, openEnd, closeStart, end, tag } = block
86
100
 
87
- tokens.push(...tokenizeHtml(code.slice(cursor, start)))
88
- tokens.push(...tokenizeHtml(code.slice(start, openEnd)))
89
- tokens.push(...(tag === 'style'
101
+ append(tokenizeHtml(code.slice(cursor, start)))
102
+ append(tokenizeHtml(code.slice(start, openEnd)))
103
+ append(tag === 'style'
90
104
  ? tokenizeCss(code.slice(openEnd, closeStart))
91
- : tokenizeJavaScript(code.slice(openEnd, closeStart), { jsx: false })))
92
- tokens.push(...tokenizeHtml(code.slice(closeStart, end)))
105
+ : tokenizeJavaScript(code.slice(openEnd, closeStart), { jsx: false }))
106
+ append(tokenizeHtml(code.slice(closeStart, end)))
93
107
  cursor = end
94
108
  }
95
109
 
96
- tokens.push(...tokenizeHtml(code.slice(cursor)))
110
+ append(tokenizeHtml(code.slice(cursor)))
97
111
  return tokens
98
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sugar-high",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/huozhi/sugar-high.git",