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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/lib/index.mjs +77 -52
  3. 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
- ![img](https://repository-images.githubusercontent.com/453236442/9416c941-9c40-4dbe-a73e-0e0d21993802)
6
+ ![img](https://repository-images.githubusercontent.com/453236442/82cf2807-78f0-4009-bbdf-d7e753f73cf4)
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 /[a-zA-Z0-9_$]/.test(chr)
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 [chr0, chr1] = [token[0], token[1]]
164
- if (isCommentStart(chr0 + chr1)) {
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
- // is quoted string
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
- if (
230
- (isStringQuotation(curr) || !jsx.tag && isRegexStart(c_n))
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
- if (!((lastType === T_SIGN && !'()[]'.includes(lastToken)) || lastType === T_COMMENT)) {
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
- let j = i + 1
240
- let strEnd = i
241
- const inString = () => j < code.length && code[j] !== '\n'
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 (isStringQuotation(curr)) {
244
- for (; inString(); j++) {
245
- if (isStringQuotation(code[j])) {
246
- strEnd = j
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 (; inString(); j++) {
253
- if (code[j] === '/' && code[j - 1] !== '\\') {
254
- strEnd = j
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 (strEnd !== i) {
260
- current = code.slice(i, strEnd + 1)
261
- append()
262
- i = j
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
- isIdentifierChar(curr) === isIdentifierChar(current[current.length - 1]) &&
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.1.2",
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
- "dev": "vite docs",
15
- "build": "vite build docs"
14
+ "test": "vitest run",
15
+ "dev": "next dev docs",
16
+ "build": "next build docs"
16
17
  },
17
18
  "devDependencies": {
18
- "vite": "2.7.13"
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
  }