feat(react-edit-benchmark): implemented AST-based mutation system
- Replaced regex-based code mutation with Babel AST. - Introduced a wider range of precise, structural bug types. - Integrated code formatting into generated benchmark fixtures. - Updated dependencies with Babel and `regexp-tree`.
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
"chalk": "^5.6.2",
|
||||
"openai": "^6.22.0",
|
||||
"partial-json": "^0.1.7",
|
||||
"zod": "4.3.6",
|
||||
"zod": "^4.3.6",
|
||||
"zod-to-json-schema": "^3.25.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -113,11 +113,16 @@
|
||||
"react-edit-benchmark": "src/index.ts",
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "7.29.1",
|
||||
"@babel/parser": "7.29.0",
|
||||
"@babel/traverse": "7.29.0",
|
||||
"@babel/types": "7.29.0",
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
"@oh-my-pi/pi-coding-agent": "workspace:*",
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
"diff": "^8.0.3",
|
||||
"prettier": "^3.8.1",
|
||||
"regexp-tree": "0.1.27",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.9",
|
||||
@@ -262,10 +267,24 @@
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.4.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.2", "@biomejs/cli-darwin-x64": "2.4.2", "@biomejs/cli-linux-arm64": "2.4.2", "@biomejs/cli-linux-arm64-musl": "2.4.2", "@biomejs/cli-linux-x64": "2.4.2", "@biomejs/cli-linux-x64-musl": "2.4.2", "@biomejs/cli-win32-arm64": "2.4.2", "@biomejs/cli-win32-x64": "2.4.2" }, "bin": { "biome": "bin/biome" } }, "sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA=="],
|
||||
@@ -754,6 +773,8 @@
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
@@ -914,6 +935,8 @@
|
||||
|
||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
@@ -1074,6 +1097,8 @@
|
||||
|
||||
"@aws-sdk/middleware-user-agent/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.990.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg=="],
|
||||
|
||||
"@babel/traverse/@babel/generator": ["@babel/generator@7.29.0", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ=="],
|
||||
|
||||
"chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"cli-truncate/string-width": ["string-width@8.1.1", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw=="],
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
"fix:rs": "cargo clippy --fix --allow-dirty --all-targets --no-deps --allow-staged --broken-code --allow-no-vcs && cargo fmt --all",
|
||||
"build:native": "bun --cwd=packages/natives run build:native",
|
||||
"dev:native": "bun --cwd=packages/natives run dev:native",
|
||||
"bench:gen-fixtures": "bun run packages/react-edit-benchmark/generate.ts --react-dir /tmp/react-source --count-per-type 3",
|
||||
"bench:gen-fixtures": "bun --cwd=packages/react-edit-benchmark run src/generate.ts --react-dir /tmp/react-source --count-per-type 8",
|
||||
"bench:edit": "bun --cwd=packages/react-edit-benchmark run start",
|
||||
"version:patch": "npm version patch -ws --no-git-tag-version && bun scripts/sync-versions.ts && rm -rf node_modules packages/*/node_modules bun.lock && bun install",
|
||||
"version:minor": "npm version minor -ws --no-git-tag-version && bun scripts/sync-versions.ts && rm -rf node_modules packages/*/node_modules bun.lock && bun install",
|
||||
|
||||
Binary file not shown.
@@ -24,11 +24,16 @@
|
||||
"bun": ">=1.3.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": "7.29.1",
|
||||
"@babel/parser": "7.29.0",
|
||||
"@babel/traverse": "7.29.0",
|
||||
"@babel/types": "7.29.0",
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
"@oh-my-pi/pi-coding-agent": "workspace:*",
|
||||
"@oh-my-pi/pi-utils": "workspace:*",
|
||||
"diff": "^8.0.3",
|
||||
"prettier": "^3.8.1"
|
||||
"prettier": "^3.8.1",
|
||||
"regexp-tree": "0.1.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.9"
|
||||
|
||||
@@ -19,16 +19,18 @@
|
||||
* - nightmare: Long files where target line repeats, minimal info
|
||||
*/
|
||||
import * as fs from "node:fs";
|
||||
import { basename, dirname, join, relative } from "node:path";
|
||||
import * as path from "node:path";
|
||||
import { parseArgs } from "node:util";
|
||||
import { TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { $ } from "bun";
|
||||
import { diffLines } from "diff";
|
||||
import { formatContent } from "./formatter";
|
||||
import { ALL_MUTATIONS, CATEGORY_MAP, type Mutation, type MutationInfo } from "./mutations";
|
||||
|
||||
const SCRIPT_DIR = import.meta.dir;
|
||||
const SUPPORTED_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx"]);
|
||||
using DEFAULT_REACT_DIR = TempDir.createSync("@react-source");
|
||||
const DEFAULT_OUTPUT = join(SCRIPT_DIR, "../fixtures.tar.gz");
|
||||
const DEFAULT_OUTPUT = path.join(SCRIPT_DIR, "../fixtures.tar.gz");
|
||||
const REACT_REPO_URL = "https://github.com/facebook/react.git";
|
||||
|
||||
const EXCLUDE_DIRS = new Set([
|
||||
@@ -105,13 +107,13 @@ function parseArguments(): Args {
|
||||
|
||||
async function ensureReactSource(reactDir: string): Promise<void> {
|
||||
if (fs.existsSync(reactDir)) {
|
||||
const packagesDir = join(reactDir, "packages");
|
||||
const packagesDir = path.join(reactDir, "packages");
|
||||
if (fs.existsSync(packagesDir)) return;
|
||||
throw new Error(`Directory exists but missing packages/: ${reactDir}`);
|
||||
}
|
||||
|
||||
console.log(`Cloning React repository to ${reactDir}…`);
|
||||
fs.mkdirSync(dirname(reactDir), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(reactDir), { recursive: true });
|
||||
const result = await $`git clone --depth 1 ${REACT_REPO_URL} ${reactDir}`.quiet().nothrow();
|
||||
if (result.exitCode !== 0) {
|
||||
const decoder = new TextDecoder();
|
||||
@@ -126,7 +128,7 @@ function isExcluded(filePath: string): boolean {
|
||||
for (const part of parts) {
|
||||
if (EXCLUDE_DIRS.has(part)) return true;
|
||||
}
|
||||
const filename = basename(filePath);
|
||||
const filename = path.basename(filePath);
|
||||
if (filename.includes(".test.") || filename.includes(".spec.") || filename.includes(".fixture.")) {
|
||||
return true;
|
||||
}
|
||||
@@ -138,14 +140,14 @@ function hasStructure(content: string): boolean {
|
||||
}
|
||||
|
||||
async function collectFiles(reactDir: string): Promise<string[]> {
|
||||
const packagesDir = join(reactDir, "packages");
|
||||
const packagesDir = path.join(reactDir, "packages");
|
||||
const candidates: string[] = [];
|
||||
|
||||
async function walk(dir: string): Promise<void> {
|
||||
if (isExcluded(dir)) return;
|
||||
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(dir, entry.name);
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await walk(fullPath);
|
||||
} else if (entry.isFile()) {
|
||||
@@ -365,27 +367,6 @@ function getCandidatesForDifficulty(files: FileEntry[], difficulty: Difficulty):
|
||||
}
|
||||
}
|
||||
|
||||
async function bunCheck(content: string, suffix: string): Promise<boolean> {
|
||||
await using tempPath = await TempDir.create("@rb-bench-check-");
|
||||
try {
|
||||
const absPath = join(tempPath.path(), suffix);
|
||||
await Bun.write(absPath, content);
|
||||
const result = await $`timeout 5s bun build ${absPath} --no-bundle`;
|
||||
return result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function isParsable(content: string, suffix: string): Promise<boolean> {
|
||||
if (content.includes("@flow")) return true;
|
||||
try {
|
||||
return await bunCheck(content, suffix);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function regionAvailable(usedLines: Map<string, number[]>, filePath: string, lineNumber: number): boolean {
|
||||
const used = usedLines.get(filePath) ?? [];
|
||||
return !used.some(ln => Math.abs(ln - lineNumber) <= 3);
|
||||
@@ -404,7 +385,7 @@ function buildPrompt(
|
||||
difficulty: Difficulty,
|
||||
entry: FileEntry,
|
||||
): string {
|
||||
const header = `# Fix the bug in \`${basename(filePath)}\``;
|
||||
const header = `# Fix the bug in \`${path.basename(filePath)}\``;
|
||||
|
||||
const isStructural = mutation.category === "structural";
|
||||
const isMultiEdit = mutation.name === "identifier-multi-edit";
|
||||
@@ -461,7 +442,7 @@ function buildPrompt(
|
||||
}
|
||||
return [
|
||||
header,
|
||||
"There is a subtle bug in this file. The fix is a one-character change.",
|
||||
"There is a subtle bug in this file.",
|
||||
"Track it down and fix it with a minimal edit.",
|
||||
].join("\n\n");
|
||||
}
|
||||
@@ -474,6 +455,70 @@ function createSeededRng(seed: number): () => number {
|
||||
};
|
||||
}
|
||||
|
||||
function countChangedHunks(original: string, mutated: string): number {
|
||||
const changes = diffLines(original, mutated);
|
||||
let hunks = 0;
|
||||
let inChangedChunk = false;
|
||||
for (const change of changes) {
|
||||
if (change.added || change.removed) {
|
||||
if (!inChangedChunk) {
|
||||
hunks++;
|
||||
inChangedChunk = true;
|
||||
}
|
||||
} else {
|
||||
inChangedChunk = false;
|
||||
}
|
||||
}
|
||||
return hunks;
|
||||
}
|
||||
|
||||
function countChangedLines(original: string, mutated: string): number {
|
||||
const changes = diffLines(original, mutated);
|
||||
let changedLines = 0;
|
||||
for (const change of changes) {
|
||||
if (!change.added && !change.removed) continue;
|
||||
const split = change.value.split("\n");
|
||||
changedLines += split.filter((line, idx) => idx < split.length - 1 || line.length > 0).length;
|
||||
}
|
||||
return changedLines;
|
||||
}
|
||||
|
||||
function countPositionalLineChanges(original: string, mutated: string): number {
|
||||
const originalLines = original.split("\n");
|
||||
const mutatedLines = mutated.split("\n");
|
||||
const max = Math.max(originalLines.length, mutatedLines.length);
|
||||
let changed = 0;
|
||||
for (let i = 0; i < max; i++) {
|
||||
if ((originalLines[i] ?? "") !== (mutatedLines[i] ?? "")) {
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function countPositionalLineHunks(original: string, mutated: string): number {
|
||||
const originalLines = original.split("\n");
|
||||
const mutatedLines = mutated.split("\n");
|
||||
const max = Math.max(originalLines.length, mutatedLines.length);
|
||||
let hunks = 0;
|
||||
let inChanged = false;
|
||||
for (let i = 0; i < max; i++) {
|
||||
const changed = (originalLines[i] ?? "") !== (mutatedLines[i] ?? "");
|
||||
if (changed) {
|
||||
if (!inChanged) {
|
||||
hunks++;
|
||||
inChanged = true;
|
||||
}
|
||||
} else {
|
||||
inChanged = false;
|
||||
}
|
||||
}
|
||||
return hunks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async function generateCase(
|
||||
rng: () => number,
|
||||
mutation: Mutation,
|
||||
@@ -486,9 +531,17 @@ async function generateCase(
|
||||
let candidates = getCandidatesForDifficulty(files, difficulty);
|
||||
if (candidates.length === 0) candidates = files;
|
||||
|
||||
let applicable = candidates.filter(entry => mutation.canApply(entry.content));
|
||||
function canApply(entry: FileEntry): boolean {
|
||||
try {
|
||||
return mutation.canApply(entry.content);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
let applicable = candidates.filter(canApply);
|
||||
if (applicable.length === 0 && candidates !== files) {
|
||||
applicable = files.filter(entry => mutation.canApply(entry.content));
|
||||
applicable = files.filter(canApply);
|
||||
}
|
||||
if (applicable.length === 0) return null;
|
||||
|
||||
@@ -496,12 +549,27 @@ async function generateCase(
|
||||
|
||||
for (let attempt = 0; attempt < attemptLimit; attempt++) {
|
||||
const entry = applicable[Math.floor(rng() * applicable.length)];
|
||||
const [mutatedContent, info] = mutation.mutate(entry.content, rng);
|
||||
let mutatedContent: string;
|
||||
let info: { lineNumber: number; originalSnippet: string; mutatedSnippet: string };
|
||||
try {
|
||||
[mutatedContent, info] = mutation.mutate(entry.content, rng);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (mutatedContent === entry.content) continue;
|
||||
if (!regionAvailable(usedLines, entry.path, info.lineNumber)) continue;
|
||||
const changedHunks = countChangedHunks(entry.content, mutatedContent);
|
||||
const changedLines = countChangedLines(entry.content, mutatedContent);
|
||||
const positionalHunks = countPositionalLineHunks(entry.content, mutatedContent);
|
||||
const positionalChanges = countPositionalLineChanges(entry.content, mutatedContent);
|
||||
const isStructural = mutation.category === "structural";
|
||||
const isMultiEdit = mutation.name === "identifier-multi-edit";
|
||||
if (!isStructural && !isMultiEdit) {
|
||||
if (changedHunks !== 1) continue;
|
||||
if (positionalHunks !== 1) continue;
|
||||
if (changedLines > 30 || positionalChanges > 30) continue;
|
||||
}
|
||||
|
||||
const suffix = `.${entry.path.split(".").pop()}`;
|
||||
if (!(await isParsable(mutatedContent, suffix))) continue;
|
||||
if (!regionAvailable(usedLines, entry.path, info.lineNumber)) continue;
|
||||
|
||||
const diffScore = scoreDifficulty(entry, info.lineNumber);
|
||||
|
||||
@@ -548,9 +616,9 @@ async function writeTarball(entries: TarEntry[], outputPath: string): Promise<vo
|
||||
await Bun.Archive.write(outputPath, data, { compress: "gzip" });
|
||||
}
|
||||
|
||||
function buildCaseEntries(result: CaseResult, reactDir: string): TarEntry[] {
|
||||
const filename = basename(result.filePath);
|
||||
const relativePath = relative(reactDir, result.filePath);
|
||||
async function buildCaseEntries(result: CaseResult, reactDir: string): Promise<TarEntry[]> {
|
||||
const filename = path.basename(result.filePath);
|
||||
const relativePath = path.relative(reactDir, result.filePath);
|
||||
const caseDir = `fixtures/${result.caseId}`;
|
||||
|
||||
const lines = result.originalContent.split("\n");
|
||||
@@ -593,9 +661,13 @@ function buildCaseEntries(result: CaseResult, reactDir: string): TarEntry[] {
|
||||
},
|
||||
};
|
||||
|
||||
const [formattedInput, formattedExpected] = await Promise.all([
|
||||
formatContent(filename, result.mutatedContent),
|
||||
formatContent(filename, result.originalContent),
|
||||
]);
|
||||
return [
|
||||
{ name: `${caseDir}/input/${filename}`, content: ensureTrailingNewline(result.mutatedContent) },
|
||||
{ name: `${caseDir}/expected/${filename}`, content: ensureTrailingNewline(result.originalContent) },
|
||||
{ name: `${caseDir}/input/${filename}`, content: ensureTrailingNewline(formattedInput.formatted) },
|
||||
{ name: `${caseDir}/expected/${filename}`, content: ensureTrailingNewline(formattedExpected.formatted) },
|
||||
{ name: `${caseDir}/prompt.md`, content: prompt },
|
||||
{ name: `${caseDir}/metadata.json`, content: JSON.stringify(metadata, null, 2) },
|
||||
];
|
||||
@@ -706,7 +778,7 @@ async function main(): Promise<number> {
|
||||
const cases = byDifficulty.get(diff) ?? [];
|
||||
console.log(`\n${diff.toUpperCase()} (${cases.length} cases):`);
|
||||
for (const result of cases.slice(0, 5)) {
|
||||
const rel = relative(reactDir, result.filePath);
|
||||
const rel = path.relative(reactDir, result.filePath);
|
||||
console.log(` ${result.caseId}: ${rel}`);
|
||||
console.log(` score=${result.difficultyScore}, lines=${result.originalContent.split("\n").length}`);
|
||||
}
|
||||
@@ -723,12 +795,12 @@ async function main(): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const tarEntries: TarEntry[] = [];
|
||||
const tarEntries: Promise<TarEntry[]>[] = [];
|
||||
for (const result of results) {
|
||||
tarEntries.push(...buildCaseEntries(result, reactDir));
|
||||
tarEntries.push(buildCaseEntries(result, reactDir));
|
||||
}
|
||||
|
||||
await writeTarball(tarEntries, args.output);
|
||||
await writeTarball((await Promise.all(tarEntries)).flat(), args.output);
|
||||
|
||||
console.log(`Generated ${results.length} cases in ${args.output}`);
|
||||
const scores = results.map(r => r.difficultyScore);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user