sugar-high 0.4.1 → 0.4.4

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.
Files changed (2) hide show
  1. package/lib/index.mjs +190 -72
  2. package/package.json +2 -2
package/lib/index.mjs CHANGED
@@ -144,8 +144,16 @@ function isIdentifier(str) {
144
144
  return isIdentifierChar(str[0]) && (str.length === 1 || isWord(str.slice(1)))
145
145
  }
146
146
 
147
+ function isStrTemplateChr(chr) {
148
+ return chr === '`'
149
+ }
150
+
151
+ function isSingleQuotes(chr) {
152
+ return chr === '"' || chr === "'"
153
+ }
154
+
147
155
  function isStringQuotation(chr) {
148
- return chr === '"' || chr === "'" || chr === '`'
156
+ return isSingleQuotes(chr) || isStrTemplateChr(chr)
149
157
  }
150
158
 
151
159
  function isCommentStart(str) {
@@ -168,16 +176,28 @@ export function tokenize(code) {
168
176
  /** @type {Array<[number, string]>} */
169
177
  const tokens = []
170
178
 
171
- // jsx.tag for entering open or closed tags
172
- // jsx.close for is the closing sign of open or close tags
173
- // jsx.child for entering children
174
- // jsx.expr for entering {expression}
175
- // jsx.end for entering close tag
176
- /** @type {{ tag: boolean; close: boolean; child: boolean; expr: boolean; end: boolean }} */
177
- const jsx = { tag: false, close: false, child: false, expr: false, end: false }
178
-
179
- const inJsxTag = () => jsx.tag || jsx.close
180
- const inJsxLiterals = () => !inJsxTag() && jsx.child && !jsx.expr
179
+ let __jsxEnter = false
180
+ /**
181
+ * @type {0 | 1 | 2}
182
+ * 0 for not in jsx
183
+ * 1 for open jsx tag
184
+ * 2 for closing jsx tag
185
+ **/
186
+ let __jsxTag = 0
187
+ let __jsxExpr = false
188
+
189
+ // only match paired (open + close) tags, not self-closing tags
190
+ let __jsxStack = 0
191
+ const __jsxChild = () => __jsxEnter && !__jsxExpr && !__jsxTag
192
+ const inJsxTag = () => __jsxTag && !__jsxChild()
193
+ const inJsxLiterals = () => !__jsxTag && __jsxChild() && !__jsxExpr && __jsxStack > 0
194
+
195
+ /** @type {string | 0} */
196
+ let __strQuote = 0
197
+ let __strTemplateExprStack = 0
198
+ let __strTemplateQuoteStack = 0
199
+ const inStrTemplateLiterals = () => (__strTemplateQuoteStack > __strTemplateExprStack)
200
+ const inStrTemplateExpr = () => __strTemplateQuoteStack > 0 && (__strTemplateQuoteStack === __strTemplateExprStack)
181
201
 
182
202
  /**
183
203
  *
@@ -185,7 +205,10 @@ export function tokenize(code) {
185
205
  * @returns {number}
186
206
  */
187
207
  function classify(token) {
188
- if (keywords.has(token)) {
208
+ const isJsxLiterals = inJsxLiterals()
209
+ if (isJsxLiterals) {
210
+ return T_JSX_LITERALS
211
+ } else if (keywords.has(token)) {
189
212
  return last[1] === '.' ? T_IDENTIFIER : T_KEYWORD
190
213
  } else if (token === '\n') {
191
214
  return T_BREAK
@@ -193,16 +216,17 @@ export function tokenize(code) {
193
216
  return T_SPACE
194
217
  } else if (token.split('').every(ch => signs.has(ch))) {
195
218
  return T_SIGN
196
- } else if (inJsxLiterals()) {
197
- return T_JSX_LITERALS
198
219
  } else if (isCls(token)) {
199
220
  return inJsxTag() ? T_IDENTIFIER : T_CLS_NUMBER
200
221
  } else {
201
- return isIdentifier(token) ? T_IDENTIFIER : T_STRING
222
+ return isIdentifier(token) && !inStrTemplateLiterals() && (__strQuote !== `"` && __strQuote !== `'`) ? T_IDENTIFIER : T_STRING
202
223
  }
203
224
  }
204
225
 
205
- const append = (_type) => {
226
+ const append = (_type, _token) => {
227
+ if (_token) {
228
+ current = _token
229
+ }
206
230
  if (current) {
207
231
  type = _type || classify(current)
208
232
  /** @type [number, string] */
@@ -220,36 +244,137 @@ export function tokenize(code) {
220
244
  const next = code[i + 1]
221
245
  const p_c = prev + curr // previous and current
222
246
  const c_n = curr + next // current and next
223
- const isJsxLiterals = inJsxLiterals()
224
247
 
225
- if (jsx.close && (last[1] === '>' || last[1] === '/>')) {
226
- jsx.close = false
248
+ if (isSingleQuotes(curr)) {
249
+ append()
250
+ if (prev !== `\\`) {
251
+ if (__strQuote && curr === __strQuote) {
252
+ __strQuote = 0
253
+ } else if (!__strQuote) {
254
+ __strQuote = curr
255
+ }
256
+ }
257
+
258
+ append(T_STRING, curr)
259
+ continue
260
+ }
261
+
262
+ if (!inStrTemplateLiterals()) {
263
+ if (prev !== '\\n' && isStrTemplateChr(curr)) {
264
+ append()
265
+ append(T_STRING, curr)
266
+ __strTemplateQuoteStack++
267
+ continue
268
+ }
269
+ }
270
+
271
+ if (inStrTemplateLiterals()) {
272
+ if (prev !== '\\n' && isStrTemplateChr(curr)) {
273
+ if (__strTemplateQuoteStack > 0) {
274
+ append()
275
+ __strTemplateQuoteStack--
276
+ append(T_STRING, curr)
277
+ continue
278
+ }
279
+ }
280
+
281
+ if (c_n === '${') {
282
+ __strTemplateExprStack++
283
+ append(T_STRING)
284
+ append(T_SIGN, c_n)
285
+ i++
286
+ continue
287
+ }
227
288
  }
228
289
 
229
- if (!jsx.tag && (c_n === '</' || c_n === '/>')) {
230
- jsx.end = true
290
+ if (inStrTemplateExpr() && curr === '}') {
291
+ append()
292
+ __strTemplateExprStack--
293
+ append(T_SIGN, curr)
294
+ continue
231
295
  }
232
296
 
233
- if (jsx.tag) {
234
- const isOpenElementEnd = curr === '>'
235
- const isCloseElementEnd = p_c === '/>'
236
- jsx.child = !jsx.end && (isOpenElementEnd && !isCloseElementEnd)
297
+ if (__jsxChild()) {
298
+ if (curr === '{') {
299
+ append()
300
+ append(T_SIGN, curr)
301
+ __jsxExpr = true
302
+ continue
303
+ }
304
+ }
305
+
306
+ if (__jsxEnter) {
307
+ // <: open tag sign
308
+ if (!__jsxTag && curr === '<') {
309
+ append()
310
+ __jsxTag = 1
311
+ current = curr
312
+ if (next === '/') {
313
+ __jsxTag = 2
314
+ current = c_n
315
+ i++
316
+ }
317
+ append(T_SIGN)
318
+ continue
319
+ }
320
+ if (__jsxTag) {
321
+ // >: open tag close sign or closing tag closing sign
322
+ if (curr === '>' && prev !== '/') {
323
+ append()
324
+ if (__jsxTag === 1) {
325
+ __jsxTag = 0
326
+ __jsxStack++
327
+ } else {
328
+ __jsxTag = 0
329
+ __jsxEnter = false
330
+ }
331
+ append(T_SIGN, curr)
332
+ continue
333
+ }
334
+
335
+ // >: tag self close sign or close tag sign
336
+ if (c_n === '/>' || c_n === '</') {
337
+ append()
338
+ if (c_n === '/>') {
339
+ __jsxTag = 0
340
+ }
341
+ if (c_n === '</') {
342
+ __jsxStack--
343
+ }
344
+ current = c_n
345
+ i++
346
+ append(T_SIGN)
347
+ continue
348
+ }
237
349
 
238
- jsx.tag = !(isOpenElementEnd || isCloseElementEnd)
239
- jsx.close = isOpenElementEnd || isCloseElementEnd
240
- if (jsx.end && (isCloseElementEnd || isOpenElementEnd)) {
241
- jsx.end = false
350
+ // <: open tag sign
351
+ if (curr === '<') {
352
+ append()
353
+ current = curr
354
+ append(T_SIGN)
355
+ continue
356
+ }
242
357
  }
243
358
  }
359
+
244
360
  // if it's not in a jsx tag declaration or a string, close child if next is jsx close tag
245
- if (!jsx.tag && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
246
- jsx.tag = true
247
- jsx.child = false
361
+ if (!__jsxTag && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
362
+ __jsxTag = next === '/' ? 2 : 1
363
+
364
+ if (curr === '<' && next !== '/') {
365
+ __jsxEnter = true
366
+ }
248
367
  }
249
368
 
250
369
  const isQuotationChar = isStringQuotation(curr)
251
- const isRegexChar = !jsx.tag && isRegexStart(c_n)
252
- if (isQuotationChar || isRegexChar) {
370
+ const isStringTemplateLiterals = inStrTemplateLiterals()
371
+ const isRegexChar = !__jsxEnter && isRegexStart(c_n)
372
+ const isJsxLiterals = inJsxLiterals()
373
+
374
+ // string quotation
375
+ if (isQuotationChar || isStringTemplateLiterals || isSingleQuotes(__strQuote)) {
376
+ current += curr
377
+ } else if (isRegexChar) {
253
378
  append()
254
379
  const [lastType, lastToken] = last
255
380
  // Special cases that are not considered as regex:
@@ -267,34 +392,23 @@ export function tokenize(code) {
267
392
  append()
268
393
  continue
269
394
  }
395
+
270
396
  const start = i++
271
- const leftQuote = code[start]
272
- let foundClose = false
273
397
 
274
398
  // end of line of end of file
275
399
  const isEof = () => i >= code.length
276
400
  const isEol = () => isEof() || code[i] === '\n'
277
- // string quotation
278
- if (isQuotationChar) {
279
- for (; !isEof(); i++) {
280
- // handle single quotes
281
- if ((leftQuote === "'" || leftQuote === '"') && code[i] === '\n') break
282
- if (isStringQuotation(code[i]) && code[i] === leftQuote) {
283
- foundClose = true
284
- break
285
- }
286
- }
287
- } else {
288
- // regex
289
- for (; !isEol(); i++) {
290
- if (code[i] === '/' && code[i - 1] !== '\\') {
291
- foundClose = true
292
- // append regex flags
293
- while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
294
- i++
295
- }
296
- break
401
+
402
+ let foundClose = false
403
+ // regex
404
+ for (; !isEol(); i++) {
405
+ if (code[i] === '/' && code[i - 1] !== '\\') {
406
+ foundClose = true
407
+ // append regex flags
408
+ while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
409
+ i++
297
410
  }
411
+ break
298
412
  }
299
413
  }
300
414
  if (start !== i && foundClose) {
@@ -327,27 +441,38 @@ export function tokenize(code) {
327
441
  )
328
442
  ) {
329
443
  current += curr
444
+ if (next === '<') {
445
+ append()
446
+ }
330
447
  } else {
331
448
  append()
332
449
  current = curr
333
450
  append()
334
451
  }
335
452
  } else {
336
- if (
453
+ if (__jsxExpr && curr === '}') {
454
+ append()
455
+ current = curr
456
+ append()
457
+ __jsxExpr = false
458
+ } else if (
337
459
  // it's jsx literals and is not a jsx bracket
338
460
  (isJsxLiterals && !jsxBrackets.has(curr)) ||
339
461
  // same type char as previous one in current token
340
- isWord(curr) === isWord(current[current.length - 1]) &&
341
- !signs.has(curr)
462
+ ((isWord(curr) === isWord(current[current.length - 1]) || __jsxChild()) && !signs.has(curr))
342
463
  ) {
343
464
  current += curr
344
- if (next === '<') {
345
- append(T_JSX_LITERALS);
346
- }
347
465
  } else {
348
- append(c_n === '</' ? T_JSX_LITERALS : undefined)
349
- current = curr
350
- if (c_n === '</' || c_n === '/>') {
466
+ if (p_c === '</') {
467
+ current = p_c
468
+ }
469
+ append()
470
+
471
+ if (p_c !== '</') {
472
+ current = curr
473
+
474
+ }
475
+ if ((c_n === '</' || c_n === '/>')) {
351
476
  current = c_n
352
477
  append()
353
478
  i++
@@ -355,13 +480,6 @@ export function tokenize(code) {
355
480
  else if (jsxBrackets.has(curr)) append()
356
481
  }
357
482
  }
358
-
359
- if (jsx.child && curr === '{') {
360
- jsx.expr = true
361
- }
362
- if (jsx.child && jsx.expr && curr === '}') {
363
- jsx.expr = false
364
- }
365
483
  }
366
484
 
367
485
  append()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sugar-high",
3
- "version": "0.4.1",
3
+ "version": "0.4.4",
4
4
  "type": "module",
5
5
  "exports": "./lib/index.mjs",
6
6
  "description": "Super lightweight JSX syntax highlighter",
@@ -16,7 +16,7 @@
16
16
  "build": "next build docs"
17
17
  },
18
18
  "devDependencies": {
19
- "codice": "^0.0.3",
19
+ "codice": "^0.0.10",
20
20
  "next": "12.1.6-canary.6",
21
21
  "react": "18.0.0",
22
22
  "react-dom": "18.0.0",