sugar-high 1.2.0 → 1.3.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.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Sugar High
2
2
 
3
- [![npm][npm-badge]][npm]
3
+ [![version][npm-version-badge]][npm]
4
+ [![downloads][npm-downloads-badge]][npm]
4
5
 
5
6
  Super lightweight syntax highlighter for JavaScript and JSX—about **1 kB** minified and gzipped. Works in the browser or any JS runtime that can set HTML strings.
6
7
 
@@ -33,7 +34,15 @@ import { rust } from 'sugar-high/presets'
33
34
  const html = highlight(source, { ...rust })
34
35
  ```
35
36
 
36
- Available presets: `c`, `css`, `diff`, `go`, `java`, `python`, and `rust`.
37
+ Available presets: `c`, `css`, `diff`, `go`, `java`, `json`, `python`, and `rust`.
38
+
39
+ The `json` preset highlights quoted object keys as `property` tokens, keeping them distinct from string values:
40
+
41
+ ```js
42
+ import { json } from 'sugar-high/presets'
43
+
44
+ const html = highlight('{"name": "Alice"}', json)
45
+ ```
37
46
 
38
47
  For more language presets and syntax color themes, see **[sugar-high.vercel.app](https://sugar-high.vercel.app/)**.
39
48
 
@@ -80,11 +89,24 @@ pre code {
80
89
  Target a line with `.sh__line:nth-child(<n>)` (1-based):
81
90
 
82
91
  ```css
92
+ .sh__line {
93
+ display: block;
94
+ padding: 0 12px;
95
+ margin: 0 -12px;
96
+ min-height: 1rem;
97
+ }
98
+
83
99
  .sh__line:nth-child(5) {
84
- background: #f5f5f5;
100
+ background: #fff8c5;
101
+ }
102
+
103
+ .sh__line--highlighted {
104
+ background: #fff8c5;
85
105
  }
86
106
  ```
87
107
 
108
+ Use `.sh__line--highlighted` when you add highlight classes yourself (for example with `lineClassName`).
109
+
88
110
  ## Remark
89
111
 
90
112
  Use the [remark plugin](https://sugar-high.vercel.app/remark) to highlight fenced code blocks when processing Markdown with [remark](https://remark.js.org/). Details: [`packages/remark-sugar-high`](https://github.com/huozhi/sugar-high/tree/main/packages/remark-sugar-high).
@@ -95,6 +117,7 @@ MIT
95
117
 
96
118
  <!-- Definitions -->
97
119
 
98
- [npm-badge]: https://img.shields.io/npm/v/sugar-high.svg
120
+ [npm-version-badge]: https://img.shields.io/npm/v/sugar-high?style=flat&colorA=000000&colorB=000000
121
+ [npm-downloads-badge]: https://img.shields.io/npm/dt/sugar-high.svg?style=flat&colorA=000000&colorB=000000
99
122
 
100
123
  [npm]: https://www.npmjs.com/package/sugar-high
package/lib/index.d.ts CHANGED
@@ -11,6 +11,8 @@ type HighlightOptions = {
11
11
  * or null/undefined or a number below 1 for default JS single-quoted string rules.
12
12
  */
13
13
  onQuote?: (curr: string, i: number, code: string) => number | null | undefined
14
+ /** Highlight quoted object keys followed by `:` as `property` tokens. */
15
+ quotedKeys?: boolean
14
16
  lineClassName?: (line: string, index: number) => string | null | undefined
15
17
  }
16
18
 
package/lib/index.js CHANGED
@@ -235,13 +235,18 @@ function isSign(ch) {
235
235
  return Signs.has(ch)
236
236
  }
237
237
 
238
+ const HtmlEntities = /** @type {Record<string, string>} */ ({
239
+ '&': '&amp;',
240
+ '<': '&lt;',
241
+ '>': '&gt;',
242
+ '"': '&quot;',
243
+ "'": '&#039;',
244
+ })
245
+
246
+ /** @param {string} str */
238
247
  function encode(str) {
239
- return str
240
- .replace(/&/g, '&amp;')
241
- .replace(/</g, '&lt;')
242
- .replace(/>/g, '&gt;')
243
- .replace(/"/g, '&quot;')
244
- .replace(/'/g, '&#039;')
248
+ if (!/[&<>"']/.test(str)) return str
249
+ return str.replace(/[&<>"']/g, chr => HtmlEntities[chr])
245
250
  }
246
251
 
247
252
  function isWord(chr) {
@@ -301,6 +306,12 @@ function isRegexStart(str) {
301
306
  return str[0] === '/' && !isCommentStart_Js(str[0], str[1])
302
307
  }
303
308
 
309
+ function isPropertyKey(code, quoteEnd) {
310
+ let i = quoteEnd + 1
311
+ while (i < code.length && /\s/.test(code[i])) i++
312
+ return code[i] === ':'
313
+ }
314
+
304
315
  /**
305
316
  * @param {string} code
306
317
  * @param {{
@@ -309,6 +320,7 @@ function isRegexStart(str) {
309
320
  * onCommentStart?: (curr: string, next: string) => number | boolean
310
321
  * onCommentEnd?: (prev: string, curr: string) => number | boolean
311
322
  * onQuote?: (curr: string, i: number, code: string) => number | null | undefined
323
+ * quotedKeys?: boolean
312
324
  * lineClassName?: (line: string, index: number) => string | null | undefined
313
325
  * } | undefined} options
314
326
  * Optional `onQuote(curr, i, code)` at `code[i] === "'"`: return length to consume from `i` (>= 1),
@@ -364,6 +376,7 @@ function tokenize(code, options) {
364
376
 
365
377
  /** @type {string | null} */
366
378
  let __strQuote = null
379
+ let __strTokenStart = 0
367
380
  let __regexQuoteStart = false
368
381
  let __strTemplateExprStack = 0
369
382
  let __strTemplateQuoteStack = 0
@@ -479,15 +492,23 @@ function tokenize(code, options) {
479
492
  // Inside jsx literals or template literals, string quotation is still part of it.
480
493
  if (isSingleQuotes(curr) && !inJsxLiterals() && !inStrTemplateLiterals()) {
481
494
  append()
495
+ let isStringClose = false
482
496
  if (prev !== `\\`) {
483
497
  if (__strQuote && curr === __strQuote) {
484
498
  __strQuote = null
499
+ isStringClose = true
485
500
  } else if (!__strQuote) {
486
501
  __strQuote = curr
502
+ __strTokenStart = tokens.length
487
503
  }
488
504
  }
489
505
 
490
506
  append(T_STRING, curr)
507
+ if (mergedOptions.quotedKeys && isStringClose && isPropertyKey(code, i)) {
508
+ for (let tokenIndex = __strTokenStart; tokenIndex < tokens.length; tokenIndex++) {
509
+ tokens[tokenIndex][0] = T_PROPERTY
510
+ }
511
+ }
491
512
  continue
492
513
  }
493
514
 
@@ -715,9 +736,16 @@ function tokenize(code, options) {
715
736
 
716
737
  let foundClose = false
717
738
 
739
+ // `/` is literal inside regex character classes, e.g. `[/]`.
740
+ let inCharClass = false
741
+
718
742
  // traverse to find closing regex slash
719
743
  for (; !isEol(); i++) {
720
- if (code[i] === '/' && code[i - 1] !== '\\') {
744
+ const ch = code[i]
745
+ const escaped = code[i - 1] === '\\'
746
+ if (!escaped && ch === '[') inCharClass = true
747
+ if (!escaped && ch === ']') inCharClass = false
748
+ if (ch === '/' && !inCharClass && !escaped) {
721
749
  foundClose = true
722
750
  // end of regex, append regex flags
723
751
  while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
@@ -763,8 +791,11 @@ function tokenize(code, options) {
763
791
  isJsxLiterals
764
792
  )
765
793
  ) {
766
- current += curr
767
- if (next === '<') {
794
+ let end = i + 1
795
+ while (code[end] === ' ') end++
796
+ current += code.slice(i, end)
797
+ i = end - 1
798
+ if (code[end] === '<') {
768
799
  append()
769
800
  }
770
801
  } else {
@@ -959,6 +990,7 @@ function toHtml(lines) {
959
990
  * onCommentStart?: (curr: string, next: string) => number | boolean
960
991
  * onCommentEnd?: (curr: string, prev: string) => number | boolean
961
992
  * onQuote?: (curr: string, i: number, code: string) => number | null | undefined
993
+ * quotedKeys?: boolean
962
994
  * lineClassName?: (line: string, index: number) => string | null | undefined
963
995
  * } | undefined} options
964
996
  * `onQuote` same as `tokenize`.
@@ -4,6 +4,7 @@ type LanguageConfig = {
4
4
  onCommentStart?(curr: string, next: string): 0 | 1 | 2
5
5
  onCommentEnd?(prev: string, curr: string): 0 | 1 | 2
6
6
  onQuote?(curr: string, i: number, code: string): number | null | undefined
7
+ quotedKeys?: boolean
7
8
  lineClassName?(line: string, index: number): string | null | undefined
8
9
  }
9
10
 
@@ -14,3 +15,4 @@ export const c: LanguageConfig
14
15
  export const go: LanguageConfig
15
16
  export const java: LanguageConfig
16
17
  export const diff: LanguageConfig
18
+ export const json: LanguageConfig
@@ -5,3 +5,4 @@ export * as c from './lang/c.js'
5
5
  export * as go from './lang/go.js'
6
6
  export * as java from './lang/java.js'
7
7
  export * as diff from './lang/diff.js'
8
+ export * as json from './lang/json.js'
@@ -0,0 +1,4 @@
1
+ // @ts-check
2
+
3
+ export const keywords = new Set(['true', 'false', 'null'])
4
+ export const quotedKeys = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sugar-high",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/huozhi/sugar-high.git",