regexp-math-test262.test.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/built-ins/RegExp/escape/initial-char-escape.js
  4. * - test/built-ins/RegExp/escape/escaped-syntax-characters-simple.js
  5. * - test/built-ins/RegExp/escape/escaped-otherpunctuators.js
  6. * - test/built-ins/RegExp/escape/escaped-control-characters.js
  7. * - test/built-ins/RegExp/escape/escaped-whitespace.js
  8. * - test/built-ins/RegExp/escape/escaped-lineterminator.js
  9. * - test/built-ins/RegExp/escape/escaped-surrogates.js
  10. * - test/built-ins/RegExp/escape/escaped-solidus-character-simple.js
  11. * - test/built-ins/RegExp/escape/escaped-utf16encodecodepoint.js
  12. * - test/built-ins/RegExp/escape/not-escaped.js
  13. * - test/built-ins/RegExp/escape/not-escaped-underscore.js
  14. * - test/built-ins/RegExp/escape/non-string-inputs.js
  15. * - test/built-ins/RegExp/prototype/hasIndices/this-val-regexp.js
  16. * - test/built-ins/RegExp/prototype/unicodeSets/uv-flags-constructor.js
  17. * - test/built-ins/RegExp/match-indices/indices-array.js
  18. * - test/built-ins/RegExp/match-indices/indices-array-matched.js
  19. * - test/built-ins/RegExp/match-indices/indices-array-non-unicode-match.js
  20. * - test/built-ins/RegExp/match-indices/indices-array-unmatched.js
  21. * - test/built-ins/RegExp/match-indices/indices-array-unicode-match.js
  22. * - test/built-ins/RegExp/match-indices/indices-groups-object-undefined.js
  23. * - test/built-ins/RegExp/match-indices/indices-groups-object-unmatched.js
  24. * - test/built-ins/RegExp/match-indices/no-indices-array.js
  25. * - test/built-ins/Math/sumPrecise/takes-iterable.js
  26. * - test/built-ins/Math/sumPrecise/sum.js
  27. * - test/built-ins/Math/sumPrecise/sum-is-infinite.js
  28. * - test/built-ins/Math/sumPrecise/sum-is-minus-zero.js
  29. * - test/built-ins/Math/sumPrecise/sum-is-NaN.js
  30. * - test/built-ins/Math/sumPrecise/throws-on-non-number.js
  31. *
  32. * Copyright (C) 2019 Ron Buckton. All rights reserved.
  33. * Copyright (C) 2021 Ron Buckton and the V8 project authors. All rights reserved.
  34. * Copyright (C) 2024 Kevin Gibbons. All rights reserved.
  35. * Copyright (C) 2024 Leo Balter. All rights reserved.
  36. * Copyright (C) 2024 Leo Balter, Jordan Harband. All rights reserved.
  37. * Copyright 2022 Mathias Bynens. All rights reserved.
  38. * Test262 portions are governed by the BSD license in LICENSE.test262.
  39. * Generator and custom-iterator cases are omitted because CodeMode exposes only its supported collection iterables;
  40. * the Math.sumPrecise iterable case additionally covers Set.
  41. */
  42. import { describe, expect, test } from "bun:test"
  43. import { Effect } from "effect"
  44. import { CodeMode } from "../src/index.js"
  45. const value = async (code: string) => {
  46. const result = await Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  47. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  48. return result.value
  49. }
  50. describe("RegExp Test262 parity", () => {
  51. test("RegExp.escape encodes syntax, punctuators, whitespace, and surrogates", async () => {
  52. expect(
  53. await value(`
  54. return [
  55. RegExp.escape("1+1"),
  56. RegExp.escape("foo.bar"),
  57. RegExp.escape(".*+?^$|()[]{}"),
  58. RegExp.escape(",-=#&!%:;@~'"),
  59. RegExp.escape("\\t\\n\\v\\f\\r"),
  60. RegExp.escape("\\uFEFF \\u00A0\\u202F"),
  61. RegExp.escape("\\u2028\\u2029"),
  62. RegExp.escape("\\uD800"),
  63. RegExp.escape("\\uDFFF"),
  64. ]
  65. `),
  66. ).toEqual([
  67. "\\x31\\+1",
  68. "\\x66oo\\.bar",
  69. "\\.\\*\\+\\?\\^\\$\\|\\(\\)\\[\\]\\{\\}",
  70. "\\x2c\\x2d\\x3d\\x23\\x26\\x21\\x25\\x3a\\x3b\\x40\\x7e\\x27",
  71. "\\t\\n\\v\\f\\r",
  72. "\\ufeff\\x20\\xa0\\u202f",
  73. "\\u2028\\u2029",
  74. "\\ud800",
  75. "\\udfff",
  76. ])
  77. })
  78. test("RegExp.escape rejects non-string inputs", async () => {
  79. expect(
  80. await value(`
  81. const rejects = (input) => {
  82. try {
  83. RegExp.escape(input)
  84. return false
  85. } catch (error) {
  86. return error.name === "TypeError"
  87. }
  88. }
  89. return [rejects(123), rejects({}), rejects([]), rejects(null), rejects(undefined)]
  90. `),
  91. ).toEqual([true, true, true, true, true])
  92. })
  93. test("RegExp.escape preserves ordinary ASCII and Unicode code points", async () => {
  94. expect(
  95. await value(`
  96. return [
  97. RegExp.escape(""),
  98. RegExp.escape(".a1b2c3D4E5F6"),
  99. RegExp.escape("_a_1_2"),
  100. RegExp.escape("hello_world"),
  101. RegExp.escape("//"),
  102. RegExp.escape("\\u4F60\\u597D"),
  103. RegExp.escape("\\u0393\\u03B5\\u03B9\\u03AC \\u03C3\\u03BF\\u03C5"),
  104. RegExp.escape("\\uD835\\uDC01"),
  105. ]
  106. `),
  107. ).toEqual([
  108. "",
  109. "\\.a1b2c3D4E5F6",
  110. "_a_1_2",
  111. "\\x68ello_world",
  112. "\\/\\/",
  113. "\u4f60\u597d",
  114. "\u0393\u03b5\u03b9\u03ac\\x20\u03c3\u03bf\u03c5",
  115. "\uD835\uDC01",
  116. ])
  117. })
  118. test("d flag exposes match indices through exec, match, and matchAll", async () => {
  119. expect(
  120. await value(`
  121. const match = /(?<a>a)(b)?/d.exec("a")
  122. const stringMatch = "a".match(/a/d)
  123. const all = "a a".matchAll(/a/dg)
  124. const blocked = /(?<constructor>a)(?<safe>b)/d.exec("ab")
  125. return [
  126. /./.hasIndices,
  127. /./d.hasIndices,
  128. new RegExp(".", "d").hasIndices,
  129. match.indices[0],
  130. match.indices[1],
  131. match.indices[2],
  132. match.indices.groups.a,
  133. stringMatch.indices[0],
  134. all[0].indices[0],
  135. all[1].indices[0],
  136. Object.keys(blocked.indices.groups),
  137. ]
  138. `),
  139. ).toEqual([false, true, true, [0, 1], [0, 1], null, [0, 1], [0, 1], [0, 1], [2, 3], ["safe"]])
  140. })
  141. test("match indices preserve captures, Unicode offsets, and groups properties", async () => {
  142. expect(
  143. await value(`
  144. const plain = /a/.exec("a")
  145. const captured = "abcd".match(/b(c)/d)
  146. const unmatched = "bab".match(/(\\w\\w)(\\W)?/d)
  147. const nonUnicode = "\\uD835\\uDC01".match(/./d)
  148. const unicode = "\\uD835\\uDC01".match(/./du)
  149. const noGroups = /./d.exec("a").indices
  150. return [
  151. Object.hasOwn(plain, "indices"),
  152. captured.indices,
  153. unmatched.indices,
  154. nonUnicode.indices[0],
  155. unicode.indices[0],
  156. Object.hasOwn(noGroups, "groups"),
  157. noGroups.groups,
  158. ]
  159. `),
  160. ).toEqual([
  161. false,
  162. [
  163. [1, 3],
  164. [2, 3],
  165. ],
  166. [[0, 2], [0, 2], null],
  167. [0, 1],
  168. [0, 2],
  169. true,
  170. null,
  171. ])
  172. })
  173. test("match and matchAll preserve named, unmatched, and blocked index groups", async () => {
  174. expect(
  175. await value(`
  176. const matched = "a".match(/(?<a>a)|(?<x>x)/d).indices.groups
  177. const all = "a x".matchAll(/(?<a>a)|(?<x>x)/dg)
  178. const blockedMatch = "ab".match(/(?<constructor>a)(?<safe>b)/d).indices.groups
  179. const blockedAll = "ab".matchAll(/(?<constructor>a)(?<safe>b)/dg)[0].indices.groups
  180. return [
  181. matched.a,
  182. matched.x,
  183. all[0].indices.groups.a,
  184. all[0].indices.groups.x,
  185. all[1].indices.groups.a,
  186. all[1].indices.groups.x,
  187. Object.keys(blockedMatch),
  188. Object.keys(blockedAll),
  189. ]
  190. `),
  191. ).toEqual([[0, 1], null, [0, 1], null, null, [2, 3], ["safe"], ["safe"]])
  192. })
  193. test("v flag exposes unicodeSets and remains exclusive with u", async () => {
  194. expect(
  195. await value(`
  196. const pattern = new RegExp("[a&&a]", "v")
  197. let rejectsUV = false
  198. try {
  199. new RegExp(".", "uv")
  200. } catch (error) {
  201. rejectsUV = error.name === "SyntaxError"
  202. }
  203. return [pattern.unicodeSets, pattern.unicode, pattern.flags, pattern.test("a"), pattern.test("b"), rejectsUV]
  204. `),
  205. ).toEqual([true, false, "v", true, false, true])
  206. })
  207. test("d and v flags compose", async () => {
  208. expect(
  209. await value(`
  210. const pattern = new RegExp("(?<a>[a&&a])", "dv")
  211. const match = pattern.exec("a")
  212. return [pattern.hasIndices, pattern.unicodeSets, pattern.flags, match.indices[0], match.indices.groups.a]
  213. `),
  214. ).toEqual([true, true, "dv", [0, 1], [0, 1]])
  215. })
  216. })
  217. describe("Math.sumPrecise Test262 parity", () => {
  218. test("performs maximally precise summation over supported iterables", async () => {
  219. expect(
  220. await value(`
  221. return [
  222. Math.sumPrecise([1, 2, 3]),
  223. Math.sumPrecise([1e30, 0.1, -1e30]),
  224. Math.sumPrecise([1e308, 1e308, 0.1, 0.1, 1e30, 0.1, -1e30, -1e308, -1e308]),
  225. Math.sumPrecise([8.98846567431158e307, 8.988465674311579e307, -1.7976931348623157e308]),
  226. Math.sumPrecise(new Set([1, 2])),
  227. [[1, 2], [3, 4]].map(Math.sumPrecise),
  228. Object.is(Math.sumPrecise([]), -0),
  229. Object.is(Math.sumPrecise([-0, -0]), -0),
  230. Object.is(Math.sumPrecise([-0, 0]), 0),
  231. ]
  232. `),
  233. ).toEqual([6, 0.1, 0.30000000000000004, 9.9792015476736e291, 3, [3, 7], true, true, true])
  234. })
  235. test("handles infinities and NaN", async () => {
  236. expect(
  237. await value(`
  238. return [
  239. Math.sumPrecise([Infinity, Infinity]) === Infinity,
  240. Math.sumPrecise([-Infinity, -Infinity]) === -Infinity,
  241. Number.isNaN(Math.sumPrecise([NaN])),
  242. Number.isNaN(Math.sumPrecise([Infinity, -Infinity])),
  243. ]
  244. `),
  245. ).toEqual([true, true, true, true])
  246. })
  247. test("rejects missing, non-iterable, sparse, and non-number inputs", async () => {
  248. expect(
  249. await value(`
  250. const rejects = (input, missing = false) => {
  251. try {
  252. if (missing) Math.sumPrecise()
  253. else Math.sumPrecise(input)
  254. return false
  255. } catch (error) {
  256. return error.name === "TypeError"
  257. }
  258. }
  259. return [
  260. rejects(undefined, true),
  261. rejects({}),
  262. rejects(["1"]),
  263. rejects(Array(1)),
  264. rejects("12"),
  265. rejects(new Map([[1, 2]])),
  266. rejects(new URLSearchParams("a=1")),
  267. ]
  268. `),
  269. ).toEqual([true, true, true, true, true, true, true])
  270. })
  271. })