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
 
x62
x62
x62
x62
x62
x62
 
 
 
 
 
 
 
 
 
x62
x873
x873
x1140
x1140
x1135
x1135
x1140
x873
x873
x873
x1
x1
x1
x873
x1
x1
x1
x873
x1
x1
x873
x873
 
 
x62
x653
x16
x16
 
 
 
x637
x637
x637
x637
x637
x637
x637
x637
x653
 
 
x62
x3348
x3348
x3348
x3348
x4421
x4421
x4421
x2213
x2213
x4421
x2208
x2208
x2208
x4421
x1140
x3348
 
 
x62
x2208
x2208
x2208
x2208
x2208
x2208
x2208
x2208
x2208
x2208











































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