Skip to content

Commit 773e6f9

Browse files
authored
chore(test): add test cases for regex literals extracted from test262 (#100)
1 parent 811bcaa commit 773e6f9

33 files changed

+328805
-1
lines changed

.eslintrc.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ module.exports = {
4949
"@eslint-community/mysticatea/ts/no-unsafe-return": "off",
5050
},
5151
},
52+
{
53+
files: ["./scripts/extract-test262.ts"],
54+
// Disables rules that cannot resolve reports due to missing library type definitions.
55+
rules: {
56+
"@eslint-community/mysticatea/ts/ban-ts-comment": "off",
57+
"@eslint-community/mysticatea/ts/no-unsafe-assignment": "off",
58+
"@eslint-community/mysticatea/ts/no-unsafe-call": "off",
59+
},
60+
},
5261

5362
{
5463
files: "./src/unicode/ids.ts",

.github/workflows/cron.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,21 @@ jobs:
2222
run: |
2323
git add --all && \
2424
git diff-index --cached HEAD --stat --exit-code
25+
check-extract-test262:
26+
name: check-extract-test262
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@v3
31+
- name: Install Node.js
32+
uses: actions/setup-node@v3
33+
with:
34+
node-version: 18
35+
- name: Install Packages
36+
run: npm install
37+
- name: Update
38+
run: npm run update:test262:extract
39+
- name: Check changes
40+
run: |
41+
git add --all && \
42+
git diff-index --cached HEAD --stat --exit-code

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"update:unicode": "run-s update:unicode:*",
5858
"update:unicode:ids": "ts-node scripts/update-unicode-ids.ts",
5959
"update:unicode:props": "ts-node scripts/update-unicode-properties.ts",
60+
"update:test262:extract": "ts-node -T scripts/extract-test262.ts",
6061
"preversion": "npm test && npm run -s build",
6162
"postversion": "git push && git push --tags",
6263
"prewatch": "npm run -s clean",
@@ -72,13 +73,16 @@
7273
"@types/node": "^12.20.55",
7374
"dts-bundle": "^0.7.3",
7475
"eslint": "^8.31.0",
76+
"js-tokens": "^8.0.1",
7577
"jsdom": "^19.0.0",
7678
"mocha": "^9.2.2",
7779
"npm-run-all": "^4.1.5",
7880
"nyc": "^14.1.1",
7981
"rimraf": "^3.0.2",
8082
"rollup": "^2.79.1",
8183
"rollup-plugin-sourcemaps": "^0.6.3",
84+
"test262": "git+https://github.com/tc39/test262.git",
85+
"test262-stream": "^1.4.0",
8286
"ts-node": "^10.9.1",
8387
"typescript": "~5.0.2"
8488
},

scripts/extract-test262.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// @ts-ignore -- ignore
2+
import TestStream from "test262-stream"
3+
import path from "path"
4+
import { promises as fs } from "fs"
5+
import { parseRegExpLiteral } from "../src/index"
6+
import jsTokens from "js-tokens"
7+
import { cloneWithoutCircular } from "./clone-without-circular"
8+
import type { RegExpSyntaxError } from "../src/regexp-syntax-error"
9+
import { fixturesData } from "../test/fixtures/parser/literal"
10+
import type { Readable } from "stream"
11+
12+
const fixturesRoot = path.join(
13+
__dirname,
14+
"../test/fixtures/parser/literal/test262",
15+
)
16+
17+
const test262Root = path.dirname(require.resolve("test262/package.json"))
18+
19+
const stream: Readable = new TestStream(test262Root, { omitRuntime: true })
20+
21+
type Test = {
22+
file: string
23+
contents: string
24+
attrs: {
25+
features?: string[]
26+
}
27+
}
28+
29+
const testObjects: Test[] = []
30+
31+
stream.on("data", (test: Test) => {
32+
if (!test.file.toLocaleLowerCase().includes("regexp")) {
33+
return
34+
}
35+
testObjects.push(test)
36+
})
37+
stream.on("end", () => {
38+
// eslint-disable-next-line no-void
39+
void extractMain()
40+
})
41+
42+
async function extractMain() {
43+
const usedPatterns = new Set<string>()
44+
for (const fixture of Object.values(fixturesData)) {
45+
for (const pattern of Object.keys(fixture.patterns)) {
46+
usedPatterns.add(pattern)
47+
}
48+
}
49+
const extractedFixtures = new Map<
50+
string,
51+
{
52+
_test262FileNames: string[]
53+
options: {
54+
strict?: boolean
55+
}
56+
patterns: Record<string, any>
57+
}
58+
>()
59+
for (const test of testObjects.sort((a, b) => {
60+
const lengthA = a.attrs.features?.length ?? 999
61+
const lengthB = b.attrs.features?.length ?? 999
62+
return lengthA - lengthB || compareStr(a.file, b.file)
63+
})) {
64+
let filePath: string | undefined = undefined
65+
if (test.attrs.features && test.attrs.features.length > 0) {
66+
filePath = path.join(
67+
fixturesRoot,
68+
`${[...test.attrs.features]
69+
.sort(compareStr)
70+
.join("-and-")}.json`,
71+
)
72+
} else {
73+
filePath = path.join(fixturesRoot, "not-categorized.json")
74+
}
75+
let fixture = extractedFixtures.get(filePath)
76+
77+
if (!fixture) {
78+
if (await fileExists(filePath)) {
79+
fixture = JSON.parse(await fs.readFile(filePath, "utf8"))
80+
}
81+
if (!fixture) {
82+
fixture = {
83+
_test262FileNames: [],
84+
options: {},
85+
patterns: {},
86+
}
87+
extractedFixtures.set(filePath, fixture)
88+
}
89+
}
90+
let has = false
91+
for (const pattern of extractRegExp(test.contents)) {
92+
if (usedPatterns.has(pattern)) {
93+
continue
94+
}
95+
has = true
96+
usedPatterns.add(pattern)
97+
try {
98+
const ast = parseRegExpLiteral(pattern, fixture.options)
99+
fixture.patterns[pattern] = { ast: cloneWithoutCircular(ast) }
100+
} catch (err) {
101+
const error = err as RegExpSyntaxError
102+
fixture.patterns[pattern] = {
103+
error: { message: error.message, index: error.index },
104+
}
105+
}
106+
}
107+
if (has) {
108+
fixture._test262FileNames = [
109+
...fixture._test262FileNames,
110+
test.file,
111+
]
112+
}
113+
}
114+
await fs.copyFile(
115+
path.join(test262Root, "LICENSE"),
116+
path.join(fixturesRoot, "LICENSE"),
117+
)
118+
for (const [filePath, fixture] of extractedFixtures) {
119+
if (Object.keys(fixture.patterns).length === 0) {
120+
continue
121+
}
122+
fixture._test262FileNames = [
123+
...new Set(fixture._test262FileNames),
124+
].sort(compareStr)
125+
// @ts-ignore -- ignore
126+
fixture.patterns = Object.fromEntries(
127+
Object.entries(fixture.patterns).sort((a, b) =>
128+
compareStr(a[0], b[0]),
129+
),
130+
)
131+
await fs.mkdir(path.dirname(filePath), { recursive: true })
132+
await fs.writeFile(
133+
filePath,
134+
JSON.stringify(
135+
fixture,
136+
(_, v: unknown) => (v === Infinity ? "$$Infinity" : v),
137+
2,
138+
),
139+
)
140+
}
141+
}
142+
143+
function* extractRegExp(content: string) {
144+
for (const token of jsTokens(content)) {
145+
if (token.type === "RegularExpressionLiteral") {
146+
yield token.value
147+
}
148+
}
149+
}
150+
151+
async function fileExists(filepath: string) {
152+
try {
153+
return (await fs.lstat(filepath)).isFile()
154+
} catch (e) {
155+
return false
156+
}
157+
}
158+
159+
function compareStr(a: string, b: string) {
160+
return a > b ? 1 : a < b ? -1 : 0
161+
}

test/fixtures/parser/literal.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type FixtureData = Record<
1818

1919
export const fixturesData: FixtureData = {}
2020
const fixturesRoot = path.join(__dirname, "literal")
21-
for (const filename of fs.readdirSync(fixturesRoot)) {
21+
for (const filename of extractFixtureFiles(fixturesRoot)) {
2222
fixturesData[filename] = JSON.parse(
2323
fs.readFileSync(path.join(fixturesRoot, filename), "utf8"),
2424
(_, v: unknown) => (v === "$$Infinity" ? Infinity : v),
@@ -37,3 +37,17 @@ export function save(): void {
3737
)
3838
}
3939
}
40+
41+
function* extractFixtureFiles(dir: string): Iterable<string> {
42+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
43+
if (dirent.isDirectory()) {
44+
for (const name of extractFixtureFiles(
45+
path.join(dir, dirent.name),
46+
)) {
47+
yield path.join(dirent.name, name)
48+
}
49+
} else if (dirent.name.endsWith(".json")) {
50+
yield dirent.name
51+
}
52+
}
53+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
The << Software identified by reference to the Ecma Standard* ("Software)">> is protected by copyright and is being
2+
made available under the "BSD License", included below. This Software may be subject to third party rights (rights
3+
from parties other than Ecma International), including patent rights, and no licenses under such third party rights
4+
are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA
5+
CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT http://www.ecma-international.org/memento/codeofconduct.htm FOR
6+
INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS*.
7+
8+
Copyright (C) 2012-2013 Ecma International
9+
All rights reserved.
10+
11+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
12+
following conditions are met:
13+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
14+
disclaimer.
15+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
16+
following disclaimer in the documentation and/or other materials provided with the distribution.
17+
3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from
18+
this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
22+
SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26+
DAMAGE.
27+
28+
* Ecma International Standards hereafter means Ecma International Standards as well as Ecma Technical Reports

0 commit comments

Comments
 (0)