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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
 
x3
x3
x3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x3
x83598
x83598
x83598
x83598
x83598































































































// 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)}`);
}