regexp.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. export const regexpMethods = new Set(["test", "exec", "toString"])
  2. export const regexpProperties = new Set([
  3. "source",
  4. "flags",
  5. "lastIndex",
  6. "global",
  7. "ignoreCase",
  8. "multiline",
  9. "sticky",
  10. "unicode",
  11. "dotAll",
  12. ])
  13. export const regexFailureReason = (error: unknown): string =>
  14. (error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "")
  15. export const escapeRegexHint =
  16. 'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.'
  17. export const toHostRegex = (arg: unknown, method: string, node: AstNode, extraFlags = ""): RegExp => {
  18. if (arg instanceof SandboxRegExp) return arg.regex
  19. if (typeof arg === "string") {
  20. try {
  21. return new RegExp(arg, extraFlags)
  22. } catch (error) {
  23. throw new InterpreterRuntimeError(
  24. `String.${method} received the string ${JSON.stringify(arg)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`,
  25. node,
  26. ).as("SyntaxError")
  27. }
  28. }
  29. throw new InterpreterRuntimeError(
  30. `String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`,
  31. node,
  32. )
  33. }
  34. export const matchToValue = (match: RegExpMatchArray): Array<unknown> => {
  35. const result: Array<unknown> = Array.from(match, (group) => group)
  36. if (match.index !== undefined) (result as Record<string, unknown> & Array<unknown>).index = match.index
  37. if (match.groups) {
  38. const groups: SafeObject = Object.create(null) as SafeObject
  39. for (const [key, group] of Object.entries(match.groups)) {
  40. if (!isBlockedMember(key)) groups[key] = group
  41. }
  42. ;(result as Record<string, unknown> & Array<unknown>).groups = groups
  43. }
  44. return result
  45. }
  46. export const invokeRegExpMethod = (
  47. value: SandboxRegExp,
  48. name: string,
  49. args: Array<unknown>,
  50. node: AstNode,
  51. ): unknown => {
  52. switch (name) {
  53. case "test":
  54. return value.regex.test(coerceToString(args[0]))
  55. case "exec": {
  56. const matched = value.regex.exec(coerceToString(args[0]))
  57. return matched === null ? null : matchToValue(matched)
  58. }
  59. case "toString":
  60. return coerceToString(value)
  61. default:
  62. throw new InterpreterRuntimeError(`RegExp method '${name}' is not available in CodeMode.`, node)
  63. }
  64. }
  65. import { type AstNode, InterpreterRuntimeError } from "../interpreter/model.js"
  66. import { isBlockedMember, type SafeObject } from "../tool-runtime.js"
  67. import { SandboxRegExp } from "../values.js"
  68. import { coerceToString } from "./value.js"