sugar-high 0.1.0 → 0.2.0

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 +4 -2
  2. package/lib/index.mjs +77 -61
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -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) {
@@ -124,7 +127,11 @@ function encode(str) {
124
127
  }
125
128
 
126
129
  function isIdentifierChar(chr) {
127
- return /[a-zA-Z0-9_$]/.test(chr)
130
+ return /^[\w_$]$/.test(chr)
131
+ }
132
+
133
+ function isIdentifier(str) {
134
+ return /[a-zA-Z_$]/.test(str[0]) && (str.length === 1 || /^[\w_$]+$/.test(str.slice(1)))
128
135
  }
129
136
 
130
137
  function isStringQuotation(chr) {
@@ -147,15 +154,9 @@ function isRegexStart(str) {
147
154
  export function tokenize(code) {
148
155
  let current = ''
149
156
  let type = -1
157
+ let last = [null, null]
150
158
  /** @type {Array<[number, string]>} */
151
159
  const tokens = []
152
- // string.type = 0 for string or string template
153
- // string.type = 1 for regex
154
- const string = { entered: false, type: 0 }
155
-
156
- // comment.type = 0 for single line comments
157
- // comment.type = 1 for multi-line comments
158
- const comment = { entered: false, type: 0 }
159
160
 
160
161
  // jsx.tag for entering open or closed tag
161
162
  // jsx.child for entering children
@@ -166,25 +167,14 @@ export function tokenize(code) {
166
167
  const inJsxLiterals = () => !jsx.tag && jsx.child && !jsx.expr
167
168
 
168
169
  function classify(token) {
169
- const [chr0, chr1] = [token[0], token[1]]
170
- if (isCommentStart(chr0 + chr1)) {
171
- return T_COMMENT
172
- } else if (keywords.has(token)) {
170
+ const chr0 = token[0]
171
+ if (keywords.has(token)) {
173
172
  return T_KEYWORD
174
173
  } else if (token === '\n') {
175
174
  return T_BREAK
176
- } else if (
177
- (
178
- // is quoted string
179
- (isStringQuotation(chr0) && !isStringQuotation(chr1)) ||
180
- // is regex
181
- (!jsx.tag && isRegexStart(chr0 + chr1) && token[token.length - 1] === '/')
182
- )
183
- ) {
184
- return T_STRING
185
- } else if (token === ' ') {
175
+ } else if (isSpaces(token)) {
186
176
  return T_SPACE
187
- } else if (signs.has(chr0)) {
177
+ } else if (token.split('').every(ch => signs.has(ch))) {
188
178
  return T_SIGN
189
179
  } else if (
190
180
  !inJsxLiterals() &&
@@ -196,14 +186,19 @@ export function tokenize(code) {
196
186
  ) {
197
187
  return T_CLS_NUMBER
198
188
  } else {
199
- return T_IDENTIFIER
189
+ return isIdentifier(token) ? T_IDENTIFIER : inJsxLiterals() ? T_JSX_LITERALS : T_STRING
200
190
  }
201
191
  }
202
192
 
203
- const append = () => {
193
+ const append = (_type) => {
204
194
  if (current) {
205
- type = classify(current)
206
- tokens.push([type, current])
195
+ type = _type || classify(current)
196
+ /** @type [number, string] */
197
+ const pair = [type, current]
198
+ if (type !== T_SPACE && type !== T_BREAK) {
199
+ last = pair
200
+ }
201
+ tokens.push(pair)
207
202
  }
208
203
  current = ''
209
204
  }
@@ -222,48 +217,69 @@ export function tokenize(code) {
222
217
  jsx.tag = !(isOpenElementEnd || isCloseElementEnd)
223
218
  }
224
219
  // if it's not in a jsx tag declaration or a string, close child if next is jsx close tag
225
- if (!jsx.tag && !string.entered && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
220
+ if (!jsx.tag && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
226
221
  jsx.tag = true
227
222
  jsx.child = false
228
223
  }
229
224
 
230
- if (
231
- !string.entered &&
232
- (isStringQuotation(curr) || !jsx.tag && isRegexStart(c_n))
233
- ) {
234
- string.entered = true
235
- string.type = isStringQuotation(curr) ? 0 : 1
225
+ const isQuotationChar = isStringQuotation(curr)
226
+ const isRegexChar = !jsx.tag && isRegexStart(c_n)
227
+ if (isQuotationChar || isRegexChar) {
236
228
  append()
237
- current = curr
238
- } else if (string.entered) {
239
- current += curr
240
- if (string.type === 0 && isStringQuotation(curr)) {
241
- string.entered = false
242
- append()
243
- } else if (string.type === 1 && prev !== '\\' && curr === '/') {
244
- string.entered = false
229
+ const [lastType, lastToken] = last
230
+ // Special cases that are not considered as regex:
231
+ // * (expr) / expr: `()[]` before `/` is still in expression
232
+ // * <non comment start>/ expr: non comment start before `/` is not regex
233
+ if (
234
+ isRegexChar &&
235
+ lastType &&
236
+ !(
237
+ (lastType === T_SIGN && !'()[]'.includes(lastToken)) ||
238
+ lastType === T_COMMENT
239
+ )
240
+ ) {
241
+ current = curr
245
242
  append()
243
+ continue
246
244
  }
247
- } else if (
248
- !comment.entered &&
249
- isCommentStart(c_n)
250
- ) {
251
- comment.type = next === '/' ? 0 : 1
252
- comment.entered = true
253
- append()
254
- current = c_n
255
- i++
256
- } else if (comment.entered) {
257
- current += curr
258
- if (comment.type === 0 && next === '\n') {
259
- comment.entered = false
260
- append()
261
- } else if (comment.type === 1 && (c_n === '*/')) {
262
- comment.entered = false
263
- current += '/'
245
+ const start = i++
246
+
247
+ const isInline = () => i < code.length && code[i] !== '\n'
248
+ // string quotation
249
+ if (isQuotationChar) {
250
+ for (; isInline(); i++) {
251
+ if (isStringQuotation(code[i])) {
252
+ break
253
+ }
254
+ }
255
+ } else {
256
+ // regex
257
+ for (; isInline(); i++) {
258
+ if (code[i] === '/' && code[i - 1] !== '\\') {
259
+ // append regex flags
260
+ while (start !== i && /^[a-z]$/.test(code[i + 1]) && isInline()) {
261
+ i++
262
+ };
263
+ break
264
+ }
265
+ }
266
+ }
267
+ if (start !== i) {
268
+ current = code.slice(start, i + 1)
269
+ append(T_STRING)
270
+ } else {
271
+ current = curr
264
272
  append()
265
- i++
266
273
  }
274
+ } else if (isCommentStart(c_n)) {
275
+ const start = i
276
+ if (next === '/') {
277
+ for (; i < code.length && code[i] !== '\n'; i++);
278
+ } else {
279
+ for (; i < code.length && code[i - 1] + code[i] !== '*/'; i++);
280
+ }
281
+ current = code.slice(start, i + 1)
282
+ append(T_COMMENT)
267
283
  } else if (curr === ' ' || curr === '\n') {
268
284
  if (
269
285
  curr === ' ' &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sugar-high",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "exports": "./lib/index.mjs",
6
6
  "description": "Super lightweight JSX syntax highlighter",
@@ -11,10 +11,12 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "scripts": {
14
+ "test": "vitest run",
14
15
  "dev": "vite docs",
15
16
  "build": "vite build docs"
16
17
  },
17
18
  "devDependencies": {
18
- "vite": "2.7.13"
19
+ "vite": "^2.8.6",
20
+ "vitest": "^0.5.9"
19
21
  }
20
22
  }