All files / testing / highlight.ts

96.00% Branches 24/25
100.00% Functions 4/4
95.89% Lines 70/73
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
 
x63
x63
x63
x63
x63
x63
 
 
 
 
 
 
 
 
 
x63
x878
x878
x1145
x1145
x1140
x1140
x1145
x878
x878
x878
x1
x1
x1
x878
x1
x1
x1
x878
x1
x1
x878
x878
 
 
x63
x653
x16
x16
 
 
 
x637
x637
x637
x637
x637
x637
x637
x637
x653
 
 
x63
x3359
x3359
x3359
x3359
x4433
x4433
x4433
x2219
x2219
x4433
x2214
x2214
x2214
x4433
x1145
x3359
 
 
x63
x2214
x2214
x2214
x2214
x2214
x2214
x2214
x2214
x2214
x2214











































I










































// Imports
import hljs from "highlight.js/lib/core"
import typescript from "highlight.js/lib/languages/typescript"
import { bgBlack, bgWhite, bgYellow, black, blue, cyan, gray, green, stripAnsiCode, underline, yellow } from "@std/fmt/colors"
import { unescape } from "@std/html/entities"
import { runtime } from "./runtime.ts"
hljs.registerLanguage("typescript", typescript)

/**
 * Syntax highlights code strings within backticks with ANSI codes.
 *
 * ```ts
 * import { highlight } from "./highlight.ts"
 * console.log(highlight("`const foo = 'bar'`"))
 * ```
 */
export function highlight(text: string, { underline: underlined = false, header = "", type = "" } = {} as { underline?: boolean; header?: string; type?: string }): string {
  text = text
    .replace(/`([^`]*?)`/g, (_, content) => {
      let highlighted = process(unescape(hljs.highlight(content, { language: "typescript" }).value))
      if (underlined) {
        highlighted = underline(highlighted)
      }
      return highlighted
    })
  let background = bgWhite
  if (type === "warn") {
    text = yellow(stripAnsiCode(text))
    background = bgYellow
  }
  if (type === "debug") {
    text = gray(stripAnsiCode(text))
    background = bgBlack
  }
  if (header) {
    text = `${background(black(` ${header} `))} ${text}`
  }
  return text
}

/** Inspects the given value and returns a string representation. */
export function inspect(value: unknown): string {
  if (typeof value === "function") {
    return "fn"
  }
  if (runtime !== "deno") {
    return `${value}`
  }
  return Deno.inspect(value, { colors: true, compact: true, depth: Infinity })
    .replace(/\n\s+/g, " ")
    .replace(/AbortSignal \{[^}]+?\}/g, "AbortSignal")
    .replace(/\[Function: ([A-Z]\w*)\]/g, "$1")
    .replace(/\[Function: (\w*)\]/g, "Function")
    .replace(/\[Function \(anonymous\)]/g, "Function")
    .replace(/\{ \[class (\w+)\] [^}]+?\}/g, "$1")
    .replace(/\[Object: null prototype\] \{ url: [^,]+?, main: [^}]+?, filename: [^,]+?, dirname: [^,]+?\}/g, "import.meta")
}

/** Process rendered highlight.js html back to cli formatted highlighing. */
function process(html: string) {
  const stack = []
  const regex = /(?<open><span[^>]*class="(?<classname>[^"]*)"[^>]*>)|(?<close><\/span>)/gs
  let match = null as ReturnType<typeof regex.exec>
  while ((match = regex.exec(html)) !== null) {
    const [captured] = match
    const { open, close, classname } = match.groups!
    if (open) {
      stack.push({ classname, a: match.index, b: match.index + captured.length })
    }
    if (close) {
      const { a, b, classname } = stack.pop()!
      return process(`${html.substring(0, a)}${color(html.substring(b, match.index), classname)}${html.substring(match.index + close.length)}`)
    }
  }
  return html
}

/** Color content according to highlight.js classname. */
function color(content: string, classname: string) {
  return ({
    "hljs-comment": gray,
    "hljs-keyword": cyan,
    "hljs-string": green,
    "hljs-title function_": blue,
    "hljs-title class_": blue,
    "hljs-literal": yellow,
    "hljs-number": yellow,
  }[classname] ?? ((text: string) => text))(content)
}