sugar-high 0.1.1 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/lib/index.mjs +67 -43
  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
@@ -74,7 +74,7 @@ const signs = new Set([
74
74
  '\\',
75
75
  ])
76
76
 
77
- const types = [
77
+ export const types = [
78
78
  'identifier',
79
79
  'keyword',
80
80
  'string',
@@ -83,6 +83,7 @@ const types = [
83
83
  'comment',
84
84
  'break',
85
85
  'space',
86
+ 'jsxliterals'
86
87
  ]
87
88
 
88
89
  /**
@@ -95,6 +96,7 @@ const types = [
95
96
  * 5 - comment
96
97
  * 6 - break
97
98
  * 7 - space
99
+ * 8 - jsx literals
98
100
  *
99
101
  */
100
102
  const [
@@ -106,6 +108,7 @@ const [
106
108
  T_COMMENT,
107
109
  T_BREAK,
108
110
  T_SPACE,
111
+ T_JSX_LITERALS,
109
112
  ] = types.map((_, i) => i)
110
113
 
111
114
  function isSpaces(str) {
@@ -123,8 +126,16 @@ function encode(str) {
123
126
  .replace(/[^\S\r\n]/g, ' ')
124
127
  }
125
128
 
129
+ function isWord(chr) {
130
+ return /^[\w_]+$/.test(chr)
131
+ }
132
+
126
133
  function isIdentifierChar(chr) {
127
- return /[a-zA-Z0-9_$]/.test(chr)
134
+ return /^[a-zA-Z_$]$/.test(chr)
135
+ }
136
+
137
+ function isIdentifier(str) {
138
+ return isIdentifierChar(str[0]) && (str.length === 1 || isWord(str.slice(1)))
128
139
  }
129
140
 
130
141
  function isStringQuotation(chr) {
@@ -147,7 +158,7 @@ function isRegexStart(str) {
147
158
  export function tokenize(code) {
148
159
  let current = ''
149
160
  let type = -1
150
- let nonSpaceType = -1
161
+ let last = [null, null]
151
162
  /** @type {Array<[number, string]>} */
152
163
  const tokens = []
153
164
 
@@ -160,45 +171,38 @@ export function tokenize(code) {
160
171
  const inJsxLiterals = () => !jsx.tag && jsx.child && !jsx.expr
161
172
 
162
173
  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)) {
174
+ const chr0 = token[0]
175
+ if (keywords.has(token)) {
167
176
  return T_KEYWORD
168
177
  } else if (token === '\n') {
169
178
  return T_BREAK
170
- } else if (token === ' ') {
179
+ } else if (isSpaces(token)) {
171
180
  return T_SPACE
172
181
  } else if (token.split('').every(ch => signs.has(ch))) {
173
182
  return T_SIGN
174
183
  } else if (
175
- (
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
184
  !inJsxLiterals() &&
185
185
  (
186
- isIdentifierChar(chr0) &&
186
+ isWord(chr0) &&
187
187
  chr0 === chr0.toUpperCase() ||
188
188
  token === 'null'
189
189
  )
190
190
  ) {
191
191
  return T_CLS_NUMBER
192
192
  } else {
193
- return T_IDENTIFIER
193
+ return isIdentifier(token) ? T_IDENTIFIER : inJsxLiterals() ? T_JSX_LITERALS : T_STRING
194
194
  }
195
195
  }
196
196
 
197
- const append = () => {
197
+ const append = (_type) => {
198
198
  if (current) {
199
- type = classify(current)
200
- if (type !== T_SPACE && type !== T_BREAK) nonSpaceType = type
201
- tokens.push([type, current])
199
+ type = _type || classify(current)
200
+ /** @type [number, string] */
201
+ const pair = [type, current]
202
+ if (type !== T_SPACE && type !== T_BREAK) {
203
+ last = pair
204
+ }
205
+ tokens.push(pair)
202
206
  }
203
207
  current = ''
204
208
  }
@@ -222,42 +226,62 @@ export function tokenize(code) {
222
226
  jsx.child = false
223
227
  }
224
228
 
225
- if (
226
- (isStringQuotation(curr) || !jsx.tag && isRegexStart(c_n))
227
- ) {
229
+ const isQuotationChar = isStringQuotation(curr)
230
+ const isRegexChar = !jsx.tag && isRegexStart(c_n)
231
+ if (isQuotationChar || isRegexChar) {
228
232
  append()
229
- if (!(nonSpaceType === T_SIGN || nonSpaceType === T_COMMENT)) {
233
+ const [lastType, lastToken] = last
234
+ // Special cases that are not considered as regex:
235
+ // * (expr1) / expr2: `()` before `/` operator is still in expression
236
+ // * <non comment start>/ expr: non comment start before `/` is not regex
237
+ if (
238
+ isRegexChar &&
239
+ lastType &&
240
+ !(
241
+ (lastType === T_SIGN && !'()'.includes(lastToken)) ||
242
+ lastType === T_COMMENT
243
+ )
244
+ ) {
230
245
  current = curr
231
246
  append()
232
247
  continue
233
248
  }
234
- let j = i + 1
235
- let strEnd = i
236
- const inString = () => j < code.length && code[j] !== '\n'
249
+ const start = i++
250
+ let foundClose = false
251
+
252
+ // end of line of end of file
253
+ const isEol = () => i >= code.length || code[i] === '\n'
237
254
  // string quotation
238
- if (isStringQuotation(curr)) {
239
- for (; inString(); j++) {
240
- if (isStringQuotation(code[j])) {
241
- strEnd = j
255
+ if (isQuotationChar) {
256
+ for (; !isEol(); i++) {
257
+ if (isStringQuotation(code[i]) && code[i] === code[start]) {
258
+ foundClose = true
242
259
  break
243
260
  }
244
261
  }
245
262
  } else {
246
263
  // regex
247
- for (; inString(); j++) {
248
- if (code[j] === '/' && code[j - 1] !== '\\') {
249
- strEnd = j
264
+ for (; !isEol(); i++) {
265
+ if (code[i] === '/' && code[i - 1] !== '\\') {
266
+ foundClose = true
267
+ // append regex flags
268
+ while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
269
+ i++
270
+ };
250
271
  break
251
272
  }
252
273
  }
253
274
  }
254
- if (strEnd !== i) {
255
- current = code.slice(i, strEnd + 1)
256
- append()
257
- i = j
275
+ if (start !== i && foundClose) {
276
+ // If current line is fully closed with string quotes or regex slashes,
277
+ // add them to tokens
278
+ current = code.slice(start, i + 1)
279
+ append(T_STRING)
258
280
  } else {
281
+ // If it doesn't match any of the above, just leave it as operator and move on
259
282
  current = curr
260
283
  append()
284
+ i = start
261
285
  }
262
286
  } else if (isCommentStart(c_n)) {
263
287
  const start = i
@@ -267,7 +291,7 @@ export function tokenize(code) {
267
291
  for (; i < code.length && code[i - 1] + code[i] !== '*/'; i++);
268
292
  }
269
293
  current = code.slice(start, i + 1)
270
- append()
294
+ append(T_COMMENT)
271
295
  } else if (curr === ' ' || curr === '\n') {
272
296
  if (
273
297
  curr === ' ' &&
@@ -285,7 +309,7 @@ export function tokenize(code) {
285
309
  } else {
286
310
  if (
287
311
  (isJsxLiterals && !jsxBrackets.has(curr)) ||
288
- isIdentifierChar(curr) === isIdentifierChar(current[current.length - 1]) &&
312
+ isWord(curr) === isWord(current[current.length - 1]) &&
289
313
  !signs.has(curr)
290
314
  ) {
291
315
  current += curr
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sugar-high",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
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.1-canary.15",
20
+ "react": "18.0.0-rc.2",
21
+ "react-dom": "18.0.0-rc.2",
22
+ "vitest": "~0.6.0"
19
23
  }
20
24
  }