sugar-high 0.4.0 → 0.4.3

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