All files / regexp / escape.ts

100.00% Branches 0/0
100.00% Lines 40/40
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
90
91
92
93
94
95
96
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
x13
 
x13
x13
x13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x13
x83638
x83638
x83638
x83638
x83638































































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.

// For future forward-compatibility with regexp `v` flag, `RESERVED_CHARS` is
// autogenerated from the `ClassSetReservedDoublePunctuator`,
// `ClassSetSyntaxCharacter`, and `ClassSetReservedPunctuator` categories in the
// {@link https://github.com/tc39/proposal-regexp-v-flag#how-is-the-v-flag-different-from-the-u-flag | draft spec}.
// ```ts
// const reservedChars = [
//   ...new Set(
//     [
//       "ClassSetReservedDoublePunctuator",
//       "ClassSetSyntaxCharacter",
//       "ClassSetReservedPunctuator",
//     ].map((n) =>
//      document.querySelector(`[name=${n}] emu-rhs`).textContent.replaceAll(
//        /\s/g,
//        "",
//      )
//    ).join(""),
//  ),
// ];
// const RESERVED_CHARS = Object.fromEntries(reservedChars
//   .map((x) => {
//     try {
//       for (const flag of "gimsuy") {
//         new RegExp(`\\${x}`, flag);
//         new RegExp(`[\\${x}]`, flag);
//       }
//       return [x, `\\${x}`];
//     } catch (e) {
//       return [x, `\\x${x.codePointAt(0).toString(16).padStart(2, "0")}`];
//     }
// }));
// ```
const RESERVED_CHARS = {
  "&": "\\x26",
  "!": "\\x21",
  "#": "\\x23",
  $: "\\$",
  "%": "\\x25",
  "*": "\\*",
  "+": "\\+",
  ",": "\\x2c",
  ".": "\\.",
  ":": "\\x3a",
  ";": "\\x3b",
  "<": "\\x3c",
  "=": "\\x3d",
  ">": "\\x3e",
  "?": "\\?",
  "@": "\\x40",
  "^": "\\^",
  "`": "\\x60",
  "~": "\\x7e",
  "(": "\\(",
  ")": "\\)",
  "[": "\\[",
  "]": "\\]",
  "{": "\\{",
  "}": "\\}",
  "/": "\\/",
  "-": "\\x2d",
  "\\": "\\\\",
  "|": "\\|",
} as const;

const RX_REGEXP_ESCAPE = new RegExp(
  `[${Object.values(RESERVED_CHARS).join("")}]`,
  "gu",
);

/**
 * Escapes arbitrary text for interpolation into a regexp, such that it will
 * match exactly that text and nothing else.
 *
 * @example Usage
 * ```ts
 * import { escape } from "@std/regexp/escape";
 * import { assertEquals, assertMatch, assertNotMatch } from "@std/assert";
 *
 * const re = new RegExp(`^${escape(".")}$`, "u");
 *
 * assertEquals("^\\.$", re.source);
 * assertMatch(".", re);
 * assertNotMatch("a", re);
 * ```
 *
 * @param str The string to escape.
 * @returns The escaped string.
 */
export function escape(str: string): string {
  return str.replaceAll(
    RX_REGEXP_ESCAPE,
    (m) => RESERVED_CHARS[m as keyof typeof RESERVED_CHARS],
  ).replace(/^[0-9a-zA-Z]/, (m) => `\\x${m.codePointAt(0)!.toString(16)}`);
}