sugar-high 0.1.2 → 0.2.2
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 +5 -3
- package/lib/index.mjs +77 -52
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Super lightweight JSX syntax highlighter, around 1KB after minified and gzipped
|
|
5
5
|
|
|
6
|
-

|
|
7
7
|
|
|
8
8
|
### Usage
|
|
9
9
|
|
|
@@ -33,7 +33,7 @@ Then make your own theme with customized colors by token type and put in global
|
|
|
33
33
|
* Class, number and null
|
|
34
34
|
* sign
|
|
35
35
|
* comment
|
|
36
|
-
*
|
|
36
|
+
* jsxliterals
|
|
37
37
|
*/
|
|
38
38
|
.sh__class {
|
|
39
39
|
color: #2d5e9d;
|
|
@@ -53,7 +53,9 @@ Then make your own theme with customized colors by token type and put in global
|
|
|
53
53
|
.sh__comment {
|
|
54
54
|
color: #a19595;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
.sh__jsxliterals {
|
|
57
|
+
color: #03066e;
|
|
58
|
+
}
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
### LICENSE
|
package/lib/index.mjs
CHANGED
|
@@ -71,10 +71,11 @@ const signs = new Set([
|
|
|
71
71
|
'[',
|
|
72
72
|
']',
|
|
73
73
|
'#',
|
|
74
|
+
'@',
|
|
74
75
|
'\\',
|
|
75
76
|
])
|
|
76
77
|
|
|
77
|
-
const types = [
|
|
78
|
+
export const types = [
|
|
78
79
|
'identifier',
|
|
79
80
|
'keyword',
|
|
80
81
|
'string',
|
|
@@ -83,10 +84,11 @@ const types = [
|
|
|
83
84
|
'comment',
|
|
84
85
|
'break',
|
|
85
86
|
'space',
|
|
87
|
+
'jsxliterals'
|
|
86
88
|
]
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
|
-
*
|
|
91
|
+
*
|
|
90
92
|
* 0 - identifier
|
|
91
93
|
* 1 - keyword
|
|
92
94
|
* 2 - string
|
|
@@ -95,7 +97,8 @@ const types = [
|
|
|
95
97
|
* 5 - comment
|
|
96
98
|
* 6 - break
|
|
97
99
|
* 7 - space
|
|
98
|
-
*
|
|
100
|
+
* 8 - jsx literals
|
|
101
|
+
*
|
|
99
102
|
*/
|
|
100
103
|
const [
|
|
101
104
|
T_IDENTIFIER,
|
|
@@ -106,6 +109,7 @@ const [
|
|
|
106
109
|
T_COMMENT,
|
|
107
110
|
T_BREAK,
|
|
108
111
|
T_SPACE,
|
|
112
|
+
T_JSX_LITERALS,
|
|
109
113
|
] = types.map((_, i) => i)
|
|
110
114
|
|
|
111
115
|
function isSpaces(str) {
|
|
@@ -123,8 +127,16 @@ function encode(str) {
|
|
|
123
127
|
.replace(/[^\S\r\n]/g, ' ')
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
function isWord(chr) {
|
|
131
|
+
return /^[\w_]+$/.test(chr)
|
|
132
|
+
}
|
|
133
|
+
|
|
126
134
|
function isIdentifierChar(chr) {
|
|
127
|
-
return
|
|
135
|
+
return /^[a-zA-Z_$]$/.test(chr)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isIdentifier(str) {
|
|
139
|
+
return isIdentifierChar(str[0]) && (str.length === 1 || isWord(str.slice(1)))
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
function isStringQuotation(chr) {
|
|
@@ -140,8 +152,8 @@ function isRegexStart(str) {
|
|
|
140
152
|
return str[0] === '/' && !isCommentStart(str[0] + str[1])
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
/**
|
|
144
|
-
* @param {string} code
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} code
|
|
145
157
|
* @return {Array<[number, string]>}
|
|
146
158
|
*/
|
|
147
159
|
export function tokenize(code) {
|
|
@@ -160,43 +172,32 @@ export function tokenize(code) {
|
|
|
160
172
|
const inJsxLiterals = () => !jsx.tag && jsx.child && !jsx.expr
|
|
161
173
|
|
|
162
174
|
function classify(token) {
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
return T_COMMENT
|
|
166
|
-
} else if (keywords.has(token)) {
|
|
175
|
+
const chr0 = token[0]
|
|
176
|
+
if (keywords.has(token)) {
|
|
167
177
|
return T_KEYWORD
|
|
168
178
|
} else if (token === '\n') {
|
|
169
179
|
return T_BREAK
|
|
170
|
-
} else if (token
|
|
180
|
+
} else if (isSpaces(token)) {
|
|
171
181
|
return T_SPACE
|
|
172
182
|
} else if (token.split('').every(ch => signs.has(ch))) {
|
|
173
183
|
return T_SIGN
|
|
174
184
|
} else if (
|
|
185
|
+
!inJsxLiterals() &&
|
|
175
186
|
(
|
|
176
|
-
|
|
177
|
-
(isStringQuotation(chr0) && !isStringQuotation(chr1)) ||
|
|
178
|
-
// is regex
|
|
179
|
-
(!jsx.tag && isRegexStart(chr0 + chr1) && token[token.length - 1] === '/')
|
|
180
|
-
)
|
|
181
|
-
) {
|
|
182
|
-
return T_STRING
|
|
183
|
-
} else if (
|
|
184
|
-
!inJsxLiterals() &&
|
|
185
|
-
(
|
|
186
|
-
isIdentifierChar(chr0) &&
|
|
187
|
+
isWord(chr0) &&
|
|
187
188
|
chr0 === chr0.toUpperCase() ||
|
|
188
189
|
token === 'null'
|
|
189
190
|
)
|
|
190
191
|
) {
|
|
191
192
|
return T_CLS_NUMBER
|
|
192
193
|
} else {
|
|
193
|
-
return T_IDENTIFIER
|
|
194
|
+
return isIdentifier(token) ? T_IDENTIFIER : inJsxLiterals() ? T_JSX_LITERALS : T_STRING
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const append = () => {
|
|
198
|
+
const append = (_type) => {
|
|
198
199
|
if (current) {
|
|
199
|
-
type = classify(current)
|
|
200
|
+
type = _type || classify(current)
|
|
200
201
|
/** @type [number, string] */
|
|
201
202
|
const pair = [type, current]
|
|
202
203
|
if (type !== T_SPACE && type !== T_BREAK) {
|
|
@@ -211,11 +212,11 @@ export function tokenize(code) {
|
|
|
211
212
|
const prev = code[i - 1]
|
|
212
213
|
const next = code[i + 1]
|
|
213
214
|
const c_n = curr + next // current and next
|
|
214
|
-
const p_c = prev + curr // previous and current
|
|
215
|
+
const p_c = prev + curr // previous and current
|
|
215
216
|
const isJsxLiterals = inJsxLiterals()
|
|
216
217
|
|
|
217
218
|
if (jsx.tag) {
|
|
218
|
-
const isOpenElementEnd = curr === '>'
|
|
219
|
+
const isOpenElementEnd = curr === '>'
|
|
219
220
|
const isCloseElementEnd = p_c === '/>'
|
|
220
221
|
jsx.child = !isCloseElementEnd && !isCloseElementEnd
|
|
221
222
|
jsx.tag = !(isOpenElementEnd || isCloseElementEnd)
|
|
@@ -226,45 +227,69 @@ export function tokenize(code) {
|
|
|
226
227
|
jsx.child = false
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
) {
|
|
230
|
+
const isQuotationChar = isStringQuotation(curr)
|
|
231
|
+
const isRegexChar = !jsx.tag && isRegexStart(c_n)
|
|
232
|
+
if (isQuotationChar || isRegexChar) {
|
|
232
233
|
append()
|
|
233
234
|
const [lastType, lastToken] = last
|
|
234
|
-
|
|
235
|
+
// Special cases that are not considered as regex:
|
|
236
|
+
// * (expr1) / expr2: `()` before `/` operator is still in expression
|
|
237
|
+
// * <non comment start>/ expr: non comment start before `/` is not regex
|
|
238
|
+
if (
|
|
239
|
+
isRegexChar &&
|
|
240
|
+
lastType &&
|
|
241
|
+
!(
|
|
242
|
+
(lastType === T_SIGN && !'()'.includes(lastToken)) ||
|
|
243
|
+
lastType === T_COMMENT
|
|
244
|
+
)
|
|
245
|
+
) {
|
|
235
246
|
current = curr
|
|
236
247
|
append()
|
|
237
248
|
continue
|
|
238
249
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
250
|
+
const start = i++
|
|
251
|
+
const leftQuote = code[start]
|
|
252
|
+
let foundClose = false
|
|
253
|
+
|
|
254
|
+
// end of line of end of file
|
|
255
|
+
const isEof = () => i >= code.length
|
|
256
|
+
const isEol = () => isEof() || code[i] === '\n'
|
|
242
257
|
// string quotation
|
|
243
|
-
if (
|
|
244
|
-
for (;
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
if (isQuotationChar) {
|
|
259
|
+
for (; !isEof(); i++) {
|
|
260
|
+
// handle single quotes
|
|
261
|
+
if ((leftQuote === "'" || leftQuote === '"') && code[i] === '\n') break
|
|
262
|
+
if (isStringQuotation(code[i]) && code[i] === leftQuote) {
|
|
263
|
+
foundClose = true
|
|
247
264
|
break
|
|
248
265
|
}
|
|
249
266
|
}
|
|
250
267
|
} else {
|
|
251
268
|
// regex
|
|
252
|
-
for (;
|
|
253
|
-
if (code[
|
|
254
|
-
|
|
269
|
+
for (; !isEol(); i++) {
|
|
270
|
+
if (code[i] === '/' && code[i - 1] !== '\\') {
|
|
271
|
+
foundClose = true
|
|
272
|
+
// append regex flags
|
|
273
|
+
while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
|
|
274
|
+
i++
|
|
275
|
+
}
|
|
255
276
|
break
|
|
256
277
|
}
|
|
257
278
|
}
|
|
258
279
|
}
|
|
259
|
-
if (
|
|
260
|
-
current
|
|
261
|
-
|
|
262
|
-
|
|
280
|
+
if (start !== i && foundClose) {
|
|
281
|
+
// If current line is fully closed with string quotes or regex slashes,
|
|
282
|
+
// add them to tokens
|
|
283
|
+
current = code.slice(start, i + 1)
|
|
284
|
+
append(T_STRING)
|
|
263
285
|
} else {
|
|
286
|
+
// If it doesn't match any of the above, just leave it as operator and move on
|
|
264
287
|
current = curr
|
|
265
288
|
append()
|
|
289
|
+
i = start
|
|
266
290
|
}
|
|
267
291
|
} else if (isCommentStart(c_n)) {
|
|
292
|
+
append()
|
|
268
293
|
const start = i
|
|
269
294
|
if (next === '/') {
|
|
270
295
|
for (; i < code.length && code[i] !== '\n'; i++);
|
|
@@ -272,7 +297,7 @@ export function tokenize(code) {
|
|
|
272
297
|
for (; i < code.length && code[i - 1] + code[i] !== '*/'; i++);
|
|
273
298
|
}
|
|
274
299
|
current = code.slice(start, i + 1)
|
|
275
|
-
append()
|
|
300
|
+
append(T_COMMENT)
|
|
276
301
|
} else if (curr === ' ' || curr === '\n') {
|
|
277
302
|
if (
|
|
278
303
|
curr === ' ' &&
|
|
@@ -290,7 +315,7 @@ export function tokenize(code) {
|
|
|
290
315
|
} else {
|
|
291
316
|
if (
|
|
292
317
|
(isJsxLiterals && !jsxBrackets.has(curr)) ||
|
|
293
|
-
|
|
318
|
+
isWord(curr) === isWord(current[current.length - 1]) &&
|
|
294
319
|
!signs.has(curr)
|
|
295
320
|
) {
|
|
296
321
|
current += curr
|
|
@@ -319,8 +344,8 @@ export function tokenize(code) {
|
|
|
319
344
|
return tokens
|
|
320
345
|
}
|
|
321
346
|
|
|
322
|
-
/**
|
|
323
|
-
* @param {Array<[number, string]>} tokens
|
|
347
|
+
/**
|
|
348
|
+
* @param {Array<[number, string]>} tokens
|
|
324
349
|
* @return {Array<string>}
|
|
325
350
|
*/
|
|
326
351
|
function generate(tokens) {
|
|
@@ -328,7 +353,7 @@ function generate(tokens) {
|
|
|
328
353
|
for (let i = 0; i < tokens.length; i++) {
|
|
329
354
|
const [type, token] = tokens[i]
|
|
330
355
|
output.push(
|
|
331
|
-
type === T_BREAK
|
|
356
|
+
type === T_BREAK
|
|
332
357
|
? '<br>'
|
|
333
358
|
: `<span class="sh__${types[type]}">${encode(token)}</span>`
|
|
334
359
|
)
|
|
@@ -337,8 +362,8 @@ function generate(tokens) {
|
|
|
337
362
|
}
|
|
338
363
|
|
|
339
364
|
/**
|
|
340
|
-
*
|
|
341
|
-
* @param {string} code
|
|
365
|
+
*
|
|
366
|
+
* @param {string} code
|
|
342
367
|
* @returns {string}
|
|
343
368
|
*/
|
|
344
369
|
export function highlight(code) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sugar-high",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": "./lib/index.mjs",
|
|
6
6
|
"description": "Super lightweight JSX syntax highlighter",
|
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"scripts": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"dev": "next dev docs",
|
|
16
|
+
"build": "next build docs"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
|
-
"
|
|
19
|
+
"next": "12.1.4",
|
|
20
|
+
"react": "18.0.0",
|
|
21
|
+
"react-dom": "18.0.0",
|
|
22
|
+
"vitest": "~0.6.0"
|
|
19
23
|
}
|
|
20
24
|
}
|