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
 
x64
x64
x64
x64
x64
x64
 
 
 
 
 
 
 
 
 
x64
x880
x880
x1147
x1147
x1142
x1142
x1147
x880
x880
x880
x1
x1
x1
x880
x1
x1
x1
x880
x1
x1
x880
x880
 
 
x64
x653
x16
x16
 
 
 
x637
x637
x637
x637
x637
x637
x637
x637
x653
 
 
x64
x3363
x3363
x3363
x3363
x4437
x4437
x4437
x2221
x2221
x4437
x2216
x2216
x2216
x4437
x1147
x3363
 
 
x64
x2216
x2216
x2216
x2216
x2216
x2216
x2216
x2216
x2216
x2216











































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)
}