miniread 1.17.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/evaluate/check-expected-evaluations.d.ts +1 -1
- package/dist/scripts/evaluate/check-expected-evaluations.js +8 -7
- package/dist/scripts/evaluate/create-evaluate-command.js +1 -2
- package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +2 -2
- package/dist/scripts/evaluate/parse-evaluate-cli-options.js +4 -2
- package/dist/scripts/evaluate/run-check-mode.d.ts +1 -1
- package/dist/scripts/evaluate/run-check-mode.js +9 -4
- package/dist/scripts/evaluate/run-evaluate-cli.js +6 -3
- package/dist/scripts/evaluate/transform-manifest.d.ts +3 -1
- package/dist/scripts/evaluate/transform-manifest.js +43 -36
- package/dist/scripts/snapshot/format-code.d.ts +1 -0
- package/dist/scripts/snapshot/format-code.js +4 -0
- package/dist/scripts/snapshot/print-snapshot-diff.d.ts +6 -0
- package/dist/scripts/snapshot/print-snapshot-diff.js +26 -0
- package/dist/scripts/snapshot/resolve-testcase-input.d.ts +16 -0
- package/dist/scripts/snapshot/resolve-testcase-input.js +30 -0
- package/dist/scripts/snapshot/run-snapshot-cli.js +67 -119
- package/dist/scripts/snapshot/run-testcase-transform.d.ts +13 -0
- package/dist/scripts/snapshot/run-testcase-transform.js +46 -0
- package/dist/transforms/_generated/manifest.d.ts +14 -0
- package/dist/transforms/_generated/manifest.js +173 -0
- package/dist/transforms/_generated/registry.d.ts +3 -0
- package/dist/transforms/_generated/registry.js +55 -0
- package/dist/transforms/expand-boolean-literals/manifest.json +6 -0
- package/dist/transforms/expand-sequence-expressions-v4/manifest.json +7 -0
- package/dist/transforms/expand-sequence-expressions-v5/manifest.json +5 -0
- package/dist/transforms/expand-special-number-literals/manifest.json +6 -0
- package/dist/transforms/expand-undefined-literals/manifest.json +6 -0
- package/dist/transforms/preset-stats.json +6 -0
- package/dist/transforms/remove-redundant-else/manifest.json +6 -0
- package/dist/transforms/rename-catch-parameters/manifest.json +6 -0
- package/dist/transforms/rename-char-code-at/manifest.json +6 -0
- package/dist/transforms/rename-charcode-variables/manifest.json +7 -0
- package/dist/transforms/rename-charcode-variables-v2/manifest.json +6 -0
- package/dist/transforms/rename-comparison-flags/manifest.json +6 -0
- package/dist/transforms/rename-destructured-aliases/manifest.json +6 -0
- package/dist/transforms/rename-event-parameters/manifest.json +5 -0
- package/dist/transforms/rename-loop-index-variables/manifest.json +7 -0
- package/dist/transforms/rename-loop-index-variables-v2/manifest.json +7 -0
- package/dist/transforms/rename-loop-index-variables-v3/manifest.json +6 -0
- package/dist/transforms/rename-parameters-to-match-properties/manifest.json +5 -0
- package/dist/transforms/rename-promise-executor-parameters/manifest.json +6 -0
- package/dist/transforms/rename-replace-child-parameters/manifest.json +6 -0
- package/dist/transforms/rename-this-aliases/manifest.json +6 -0
- package/dist/transforms/rename-timeout-ids/manifest.json +6 -0
- package/dist/transforms/rename-use-reference-guards/manifest.json +7 -0
- package/dist/transforms/rename-use-reference-guards-v2/manifest.json +5 -0
- package/dist/transforms/simplify-boolean-negations/manifest.json +5 -0
- package/dist/transforms/simplify-boolean-negations/simplify-boolean-negations-transform.d.ts +2 -0
- package/dist/transforms/simplify-boolean-negations/simplify-boolean-negations-transform.js +90 -0
- package/dist/transforms/split-variable-declarations/manifest.json +6 -0
- package/dist/transforms/transform-presets.js +2 -17
- package/dist/transforms/transform-registry.d.ts +1 -3
- package/dist/transforms/transform-registry.js +1 -51
- package/package.json +5 -6
- package/dist/scripts/evaluate/parse-transform-manifest.d.ts +0 -22
- package/dist/scripts/evaluate/parse-transform-manifest.js +0 -29
- package/transform-manifest.json +0 -251
|
@@ -10,7 +10,7 @@ type CheckExpectedEvaluationsResult = {
|
|
|
10
10
|
allPresent: boolean;
|
|
11
11
|
};
|
|
12
12
|
type CheckExpectedEvaluationsOptions = {
|
|
13
|
-
|
|
13
|
+
transformsDirectory: string;
|
|
14
14
|
testCasesDirectory: string;
|
|
15
15
|
};
|
|
16
16
|
export declare const checkExpectedEvaluations: (options: CheckExpectedEvaluationsOptions) => Promise<CheckExpectedEvaluationsResult>;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { allTransformIds } from "../../transforms/transform-registry.js";
|
|
3
4
|
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
4
5
|
import { transformPresets } from "../../transforms/transform-presets.js";
|
|
5
|
-
import { parseTransformManifest } from "./parse-transform-manifest.js";
|
|
6
6
|
import { checkAllSnapshots } from "./check-snapshots.js";
|
|
7
7
|
export const checkExpectedEvaluations = async (options) => {
|
|
8
|
-
const {
|
|
8
|
+
const { transformsDirectory, testCasesDirectory } = options;
|
|
9
9
|
const transformIds = allTransformIds.toSorted();
|
|
10
10
|
const missingManifestEntries = [];
|
|
11
|
-
// Check manifest
|
|
12
|
-
const manifestContent = await fs.readFile(manifestPath, "utf8");
|
|
13
|
-
const manifest = parseTransformManifest(manifestContent);
|
|
14
|
-
const manifestIds = new Set(manifest.transforms.map((t) => t.id));
|
|
11
|
+
// Check that each transform has a manifest.json file
|
|
15
12
|
for (const transformId of transformIds) {
|
|
16
|
-
|
|
13
|
+
const manifestPath = path.join(transformsDirectory, transformId, "manifest.json");
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(manifestPath);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
17
18
|
missingManifestEntries.push({
|
|
18
19
|
transformId,
|
|
19
20
|
path: manifestPath,
|
|
@@ -22,8 +22,7 @@ export const createEvaluateCommand = (options) => {
|
|
|
22
22
|
.option("--write-metrics", "Write metrics JSON to the evaluation directory")
|
|
23
23
|
.option("--metrics-file <path>", "Write metrics JSON to a specific path")
|
|
24
24
|
.option("--write-patches", "Write baseline/test diff patches to the evaluation directory")
|
|
25
|
-
.option("-u, --update-manifest", "Update
|
|
26
|
-
.option("--manifest-file <path>", "Path to transform-manifest.json", "transform-manifest.json")
|
|
25
|
+
.option("-u, --update-manifest", "Update manifest.json metrics for transforms (does not add new entries or edit notes)")
|
|
27
26
|
.option("-f, --overwrite", "Overwrite existing metrics/patch files")
|
|
28
27
|
.option("--no-format-code", "Skip formatting temp directories with Prettier before diffing")
|
|
29
28
|
.option("--check", "Check that all expected evaluation metrics files exist (does not run evaluations)");
|
|
@@ -12,7 +12,6 @@ export type EvaluateCliRawOptions = {
|
|
|
12
12
|
metricsFile?: string;
|
|
13
13
|
writePatches?: boolean;
|
|
14
14
|
updateManifest?: boolean;
|
|
15
|
-
manifestFile: string;
|
|
16
15
|
overwrite?: boolean;
|
|
17
16
|
formatCode: boolean;
|
|
18
17
|
check?: boolean;
|
|
@@ -30,7 +29,8 @@ export type EvaluateCliOptions = {
|
|
|
30
29
|
shouldWritePatches: boolean;
|
|
31
30
|
shouldUpdateManifest: boolean;
|
|
32
31
|
metricsPath: string;
|
|
33
|
-
|
|
32
|
+
transformsDirectory: string;
|
|
33
|
+
presetStatsPath: string;
|
|
34
34
|
overwrite: boolean;
|
|
35
35
|
formatCode: boolean;
|
|
36
36
|
baselineSlug: string;
|
|
@@ -77,7 +77,8 @@ export const parseEvaluateCliOptions = (options) => {
|
|
|
77
77
|
});
|
|
78
78
|
const sourcesDirectory = path.resolve(cwd, rawOptions.sources);
|
|
79
79
|
const evaluationDirectory = path.resolve(cwd, rawOptions.evaluationDir);
|
|
80
|
-
const
|
|
80
|
+
const transformsDirectory = path.resolve(cwd, "src/transforms");
|
|
81
|
+
const presetStatsPath = path.resolve(cwd, "src/transforms/preset-stats.json");
|
|
81
82
|
const shouldWriteMetrics = Boolean(rawOptions.writeMetrics || rawOptions.metricsFile);
|
|
82
83
|
const shouldWritePatches = Boolean(rawOptions.writePatches);
|
|
83
84
|
const shouldUpdateManifest = Boolean(rawOptions.updateManifest);
|
|
@@ -97,7 +98,8 @@ export const parseEvaluateCliOptions = (options) => {
|
|
|
97
98
|
shouldWritePatches,
|
|
98
99
|
shouldUpdateManifest,
|
|
99
100
|
metricsPath,
|
|
100
|
-
|
|
101
|
+
transformsDirectory,
|
|
102
|
+
presetStatsPath,
|
|
101
103
|
overwrite,
|
|
102
104
|
formatCode,
|
|
103
105
|
baselineSlug,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { checkExpectedEvaluations } from "./check-expected-evaluations.js";
|
|
3
3
|
export const runCheckMode = async (options) => {
|
|
4
|
-
const {
|
|
4
|
+
const { transformsDirectory, rootDirectory, format } = options;
|
|
5
5
|
const checkResult = await checkExpectedEvaluations({
|
|
6
|
-
|
|
6
|
+
transformsDirectory,
|
|
7
7
|
testCasesDirectory: path.join(rootDirectory, "test-cases"),
|
|
8
8
|
});
|
|
9
9
|
if (format === "json") {
|
|
@@ -33,11 +33,16 @@ export const runCheckMode = async (options) => {
|
|
|
33
33
|
}
|
|
34
34
|
let exitCode = 0;
|
|
35
35
|
if (checkResult.missingManifestEntries.length > 0) {
|
|
36
|
-
console.error(`\n[MISSING MANIFEST
|
|
36
|
+
console.error(`\n[MISSING MANIFEST FILES] ${checkResult.missingManifestEntries.length} transform(s) missing manifest.json:`);
|
|
37
37
|
for (const item of checkResult.missingManifestEntries) {
|
|
38
38
|
console.error(` - ${item.transformId}`);
|
|
39
39
|
}
|
|
40
|
-
console.error(`\n To fix:
|
|
40
|
+
console.error(`\n To fix: Create a manifest.json file in each transform directory with the following structure:`);
|
|
41
|
+
console.error(` {`);
|
|
42
|
+
console.error(` "diffReductionImpact": 0,`);
|
|
43
|
+
console.error(` "recommended": true,`);
|
|
44
|
+
console.error(` "notes": "Description of the transform's impact"`);
|
|
45
|
+
console.error(` }`);
|
|
41
46
|
exitCode = 1;
|
|
42
47
|
}
|
|
43
48
|
const missingSnapshots = checkResult.snapshotIssues.filter((issue) => issue.issue === "missing-required");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
3
|
+
import { transformPresets } from "../../transforms/transform-presets.js";
|
|
3
4
|
import { createEvaluateCommand } from "./create-evaluate-command.js";
|
|
4
5
|
import { parseEvaluateCliOptions, } from "./parse-evaluate-cli-options.js";
|
|
5
6
|
import { resolveEvaluateDependencies } from "./resolve-evaluate-dependencies.js";
|
|
@@ -35,7 +36,7 @@ export const runEvaluateCli = async (argv) => {
|
|
|
35
36
|
};
|
|
36
37
|
if (parsedOptions.checkMode) {
|
|
37
38
|
return runCheckMode({
|
|
38
|
-
|
|
39
|
+
transformsDirectory: parsedOptions.transformsDirectory,
|
|
39
40
|
rootDirectory: process.cwd(),
|
|
40
41
|
format: parsedOptions.format,
|
|
41
42
|
});
|
|
@@ -100,15 +101,17 @@ export const runEvaluateCli = async (argv) => {
|
|
|
100
101
|
}
|
|
101
102
|
if (parsedOptions.shouldUpdateManifest) {
|
|
102
103
|
if (errors.length > 0) {
|
|
103
|
-
console.error("Refusing to update
|
|
104
|
+
console.error("Refusing to update manifests because some evaluations failed.");
|
|
104
105
|
exitCode = 1;
|
|
105
106
|
}
|
|
106
107
|
else {
|
|
107
108
|
const updateResult = await updateTransformManifestFromEvaluation({
|
|
108
|
-
|
|
109
|
+
transformsDirectory: parsedOptions.transformsDirectory,
|
|
110
|
+
presetStatsPath: parsedOptions.presetStatsPath,
|
|
109
111
|
baselineTransforms: parsedOptions.baselineTransforms,
|
|
110
112
|
testTransforms: parsedOptions.testTransforms,
|
|
111
113
|
reductionRatios: results.map((r) => r.reductionRatio),
|
|
114
|
+
recommendedTransformIds: transformPresets.recommended,
|
|
112
115
|
});
|
|
113
116
|
console.error(updateResult.message);
|
|
114
117
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
type UpdateTransformManifestOptions = {
|
|
2
|
-
|
|
2
|
+
transformsDirectory: string;
|
|
3
|
+
presetStatsPath: string;
|
|
3
4
|
baselineTransforms: string[];
|
|
4
5
|
testTransforms: string[];
|
|
5
6
|
reductionRatios: number[];
|
|
7
|
+
recommendedTransformIds: string[];
|
|
6
8
|
};
|
|
7
9
|
export declare const updateTransformManifestFromEvaluation: (options: UpdateTransformManifestOptions) => Promise<{
|
|
8
10
|
updated: boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { haveSameIds, normalizeTransformIds, uniqueIds, } from "./transform-id-set.js";
|
|
3
|
-
import { parseTransformManifest } from "./parse-transform-manifest.js";
|
|
4
4
|
import { writeJsonFileAtomic } from "./write-text-file-atomic.js";
|
|
5
|
+
import presetStats from "../../transforms/preset-stats.json" with { type: "json" };
|
|
5
6
|
const average = (values) => {
|
|
6
7
|
if (values.length === 0)
|
|
7
8
|
return 0;
|
|
@@ -11,73 +12,79 @@ const average = (values) => {
|
|
|
11
12
|
}
|
|
12
13
|
return total / values.length;
|
|
13
14
|
};
|
|
14
|
-
|
|
15
|
-
const { manifestPath, baselineTransforms, testTransforms, reductionRatios } = options;
|
|
15
|
+
const loadManifest = async (manifestPath) => {
|
|
16
16
|
try {
|
|
17
|
-
await fs.
|
|
17
|
+
const content = await fs.readFile(manifestPath, "utf8");
|
|
18
|
+
return JSON.parse(content);
|
|
18
19
|
}
|
|
19
|
-
catch {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
};
|
|
27
|
+
export const updateTransformManifestFromEvaluation = async (options) => {
|
|
28
|
+
const { transformsDirectory, presetStatsPath, baselineTransforms, testTransforms, reductionRatios, recommendedTransformIds, } = options;
|
|
27
29
|
const baselineIds = normalizeTransformIds(baselineTransforms);
|
|
28
30
|
const testIds = normalizeTransformIds(testTransforms);
|
|
29
|
-
const idsToUpdate = new Set([...baselineIds, ...testIds]);
|
|
30
|
-
idsToUpdate.delete("none");
|
|
31
|
-
const missingIds = [...idsToUpdate].filter((id) => !existingIds.has(id));
|
|
32
|
-
let updated = false;
|
|
33
31
|
const isBaselineNone = baselineIds.length === 0 ||
|
|
34
32
|
(baselineTransforms.length === 1 && baselineTransforms[0] === "none");
|
|
33
|
+
let updated = false;
|
|
34
|
+
const messages = [];
|
|
35
|
+
// Update individual transform manifest if baseline is none and we have a single test transform
|
|
35
36
|
if (isBaselineNone && testIds.length === 1) {
|
|
36
37
|
const transformId = testIds[0];
|
|
37
38
|
if (transformId) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const manifestPath = path.join(transformsDirectory, transformId, "manifest.json");
|
|
40
|
+
const manifest = await loadManifest(manifestPath);
|
|
41
|
+
if (manifest) {
|
|
42
|
+
const measuredImpact = average(reductionRatios);
|
|
43
|
+
if (manifest.diffReductionImpact !== measuredImpact) {
|
|
44
|
+
manifest.diffReductionImpact = measuredImpact;
|
|
45
|
+
manifest.evaluatedAt = new Date().toISOString();
|
|
46
|
+
await writeJsonFileAtomic(manifestPath, manifest, {
|
|
47
|
+
overwrite: true,
|
|
48
|
+
});
|
|
49
|
+
messages.push(`Updated ${transformId}/manifest.json`);
|
|
50
|
+
updated = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
messages.push(`Skipping update for ${transformId}: manifest.json not found at ${manifestPath}`);
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
}
|
|
58
|
+
// Update preset stats if testing the recommended preset
|
|
46
59
|
if (isBaselineNone) {
|
|
47
|
-
const recommendedIds = manifest.transforms
|
|
48
|
-
.filter((entry) => entry.recommended)
|
|
49
|
-
.map((entry) => entry.id);
|
|
50
60
|
const testUniqueIds = uniqueIds(testIds);
|
|
51
|
-
const recommendedUniqueIds = uniqueIds(
|
|
61
|
+
const recommendedUniqueIds = uniqueIds(recommendedTransformIds);
|
|
52
62
|
if (testUniqueIds.length > 0 &&
|
|
53
63
|
haveSameIds(testUniqueIds, recommendedUniqueIds)) {
|
|
54
64
|
const measuredImpact = average(reductionRatios);
|
|
55
|
-
|
|
56
|
-
const existing =
|
|
65
|
+
const currentStats = { ...presetStats };
|
|
66
|
+
const existing = currentStats.recommended;
|
|
57
67
|
if (existing?.diffReductionImpact !== measuredImpact) {
|
|
58
|
-
|
|
68
|
+
currentStats.recommended = {
|
|
59
69
|
diffReductionImpact: measuredImpact,
|
|
60
70
|
notes: `Measured with baseline none: ${(measuredImpact * 100).toFixed(2)}%.`,
|
|
61
71
|
};
|
|
72
|
+
await writeJsonFileAtomic(presetStatsPath, currentStats, {
|
|
73
|
+
overwrite: true,
|
|
74
|
+
});
|
|
75
|
+
messages.push("Updated preset-stats.json");
|
|
62
76
|
updated = true;
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
}
|
|
66
80
|
if (!updated) {
|
|
67
|
-
const missingMessage = missingIds.length > 0
|
|
68
|
-
? ` Missing transform entries: ${missingIds.join(", ")}. Add them to transform-manifest.json manually.`
|
|
69
|
-
: "";
|
|
70
81
|
return {
|
|
71
82
|
updated: false,
|
|
72
|
-
message:
|
|
83
|
+
message: messages.length > 0 ? messages.join("\n") : "No updates needed.",
|
|
73
84
|
};
|
|
74
85
|
}
|
|
75
|
-
await writeJsonFileAtomic(manifestPath, manifest, { overwrite: true });
|
|
76
|
-
const missingMessage = missingIds.length > 0
|
|
77
|
-
? ` Missing transform entries: ${missingIds.join(", ")}. Add them to transform-manifest.json manually.`
|
|
78
|
-
: "";
|
|
79
86
|
return {
|
|
80
87
|
updated: true,
|
|
81
|
-
message:
|
|
88
|
+
message: messages.join("\n"),
|
|
82
89
|
};
|
|
83
90
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const formatCode: (code: string) => Promise<string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const printSnapshotDiff = (options) => {
|
|
2
|
+
const { expectedContent, actualOutput } = options;
|
|
3
|
+
console.log(`\nDiff: expected vs actual`);
|
|
4
|
+
console.log("---");
|
|
5
|
+
const expectedLines = expectedContent.split("\n");
|
|
6
|
+
const actualLines = actualOutput.split("\n");
|
|
7
|
+
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
8
|
+
for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
|
|
9
|
+
const expectedLine = expectedLines[lineIndex];
|
|
10
|
+
const actualLine = actualLines[lineIndex];
|
|
11
|
+
if (expectedLine === actualLine)
|
|
12
|
+
continue;
|
|
13
|
+
console.log(`Line ${lineIndex + 1}:`);
|
|
14
|
+
if (expectedLine !== undefined && actualLine === undefined) {
|
|
15
|
+
console.log(` - ${expectedLine}`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (expectedLine === undefined && actualLine !== undefined) {
|
|
19
|
+
console.log(` + ${actualLine}`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
console.log(` - ${expectedLine}`);
|
|
23
|
+
console.log(` + ${actualLine}`);
|
|
24
|
+
}
|
|
25
|
+
console.log("---");
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type ResolveTestcaseInputOptions = {
|
|
2
|
+
testcase: string;
|
|
3
|
+
};
|
|
4
|
+
type ResolveTestcaseInputValue = {
|
|
5
|
+
testcaseDirectory: string;
|
|
6
|
+
basePath: string;
|
|
7
|
+
};
|
|
8
|
+
type Result<T> = {
|
|
9
|
+
ok: true;
|
|
10
|
+
value: T;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
error: Error;
|
|
14
|
+
};
|
|
15
|
+
export declare const resolveTestcaseInput: (options: ResolveTestcaseInputOptions) => Promise<Result<ResolveTestcaseInputValue>>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const TEST_CASES_DIR = path.resolve(import.meta.dirname, "../../../test-cases");
|
|
4
|
+
export const resolveTestcaseInput = async (options) => {
|
|
5
|
+
const { testcase } = options;
|
|
6
|
+
const testcaseDirectory = path.join(TEST_CASES_DIR, testcase);
|
|
7
|
+
try {
|
|
8
|
+
const stats = await fs.stat(testcaseDirectory);
|
|
9
|
+
if (!stats.isDirectory()) {
|
|
10
|
+
return { ok: false, error: new Error(`${testcase} is not a directory`) };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
error: new Error(`Test case directory not found: test-cases/${testcase}`),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const basePath = path.join(testcaseDirectory, "base.js");
|
|
20
|
+
try {
|
|
21
|
+
await fs.access(basePath);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: new Error(`base.js not found in test-cases/${testcase}/`),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { ok: true, value: { testcaseDirectory, basePath } };
|
|
30
|
+
};
|
|
@@ -1,75 +1,39 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import path from "node:path";
|
|
4
|
-
import * as prettier from "prettier";
|
|
5
3
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
6
|
-
import {
|
|
7
|
-
import { transformPresets } from "../../transforms/transform-presets.js";
|
|
8
|
-
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
4
|
+
import { formatCode } from "./format-code.js";
|
|
9
5
|
import { createSnapshotCommand, } from "./create-snapshot-command.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const TEST_CASES_DIR = path.resolve(import.meta.dirname, "../../../test-cases");
|
|
14
|
-
const formatCode = async (code) => prettier.format(code, { parser: "babel" });
|
|
15
|
-
const runTransform = async (inputPath, transformId) => {
|
|
16
|
-
const content = await fs.readFile(inputPath, "utf8");
|
|
17
|
-
// Handle "recommended" as a special case that runs all recommended transforms
|
|
18
|
-
const transformIds = transformId === "recommended"
|
|
19
|
-
? transformPresets.recommended
|
|
20
|
-
: [transformId];
|
|
21
|
-
const transforms = transformIds.map((id) => {
|
|
22
|
-
const transform = transformRegistry[id];
|
|
23
|
-
if (!transform) {
|
|
24
|
-
throw new Error(`Unknown transform: ${id}. Run 'miniread --list-transforms' to see available transforms.`);
|
|
25
|
-
}
|
|
26
|
-
return transform;
|
|
27
|
-
});
|
|
28
|
-
const projectGraph = buildProjectGraph([{ path: inputPath, content }]);
|
|
29
|
-
for (const transform of transforms) {
|
|
30
|
-
await transform.transform({
|
|
31
|
-
projectGraph,
|
|
32
|
-
currentFile: undefined,
|
|
33
|
-
options: {},
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
const fileInfo = projectGraph.files.get(inputPath);
|
|
37
|
-
if (!fileInfo) {
|
|
38
|
-
return content;
|
|
39
|
-
}
|
|
40
|
-
return generate(fileInfo.ast).code;
|
|
41
|
-
};
|
|
6
|
+
import { printSnapshotDiff } from "./print-snapshot-diff.js";
|
|
7
|
+
import { resolveTestcaseInput } from "./resolve-testcase-input.js";
|
|
8
|
+
import { runTestcaseTransform } from "./run-testcase-transform.js";
|
|
42
9
|
export const runSnapshotCli = async (argv) => {
|
|
43
10
|
const program = createSnapshotCommand({ version: packageJson.version });
|
|
44
11
|
program.parse(argv.filter((argument) => argument !== "--"));
|
|
45
12
|
const rawOptions = program.opts();
|
|
46
13
|
const { testcase, transform, expected } = rawOptions;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.error(`Error: ${testcase} is not a directory`);
|
|
14
|
+
const resolved = await resolveTestcaseInput({ testcase });
|
|
15
|
+
if (!resolved.ok) {
|
|
16
|
+
const message = resolved.error.message;
|
|
17
|
+
if (message === `${testcase} is not a directory`) {
|
|
18
|
+
console.error(`Error: ${message}`);
|
|
53
19
|
return 1;
|
|
54
20
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
catch {
|
|
67
|
-
console.error(`Error: base.js not found in test-cases/${testcase}/`);
|
|
68
|
-
console.error(`Create test-cases/${testcase}/base.js with your minified snippet first.`);
|
|
21
|
+
if (message.startsWith("Test case directory not found:")) {
|
|
22
|
+
console.error(`Error: ${message}`);
|
|
23
|
+
console.error(`Create it first: mkdir -p test-cases/${testcase}`);
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
if (message.startsWith("base.js not found in test-cases/")) {
|
|
27
|
+
console.error(`Error: ${message}`);
|
|
28
|
+
console.error(`Create test-cases/${testcase}/base.js with your minified snippet first.`);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
console.error(`Error: ${message}`);
|
|
69
32
|
return 1;
|
|
70
33
|
}
|
|
71
|
-
const
|
|
72
|
-
const
|
|
34
|
+
const { testcaseDirectory, basePath } = resolved.value;
|
|
35
|
+
const expectedPath = path.join(testcaseDirectory, `${transform}-expected.js`);
|
|
36
|
+
const actualPath = path.join(testcaseDirectory, `${transform}.js`);
|
|
73
37
|
if (expected) {
|
|
74
38
|
// Expected-file workflow
|
|
75
39
|
// Step 1: Check if expected file exists, create from base.js if not
|
|
@@ -83,69 +47,53 @@ export const runSnapshotCli = async (argv) => {
|
|
|
83
47
|
await fs.writeFile(expectedPath, formattedBase);
|
|
84
48
|
console.log(`Created: test-cases/${testcase}/${transform}-expected.js`);
|
|
85
49
|
console.log("Edit this file to match your expected output, then re-run.");
|
|
50
|
+
// Design phase: allow creating the expected file before the transform exists.
|
|
51
|
+
// The next run (after implementing the transform) will write the actual output
|
|
52
|
+
// and compare it against this expected snapshot.
|
|
86
53
|
return 0;
|
|
87
54
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// Diff - show the difference
|
|
116
|
-
console.log(`\nDiff: expected vs actual`);
|
|
117
|
-
console.log("---");
|
|
118
|
-
// Simple line-by-line diff output
|
|
119
|
-
const expectedLines = expectedContent.split("\n");
|
|
120
|
-
const actualLines = actualOutput.split("\n");
|
|
121
|
-
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
122
|
-
let hasDiff = false;
|
|
123
|
-
for (let lineIndex = 0; lineIndex < maxLines; lineIndex++) {
|
|
124
|
-
const exp = expectedLines[lineIndex];
|
|
125
|
-
const act = actualLines[lineIndex];
|
|
126
|
-
if (exp !== act) {
|
|
127
|
-
hasDiff = true;
|
|
128
|
-
if (exp !== undefined && act === undefined) {
|
|
129
|
-
console.log(`Line ${lineIndex + 1}:`);
|
|
130
|
-
console.log(` - ${exp}`);
|
|
131
|
-
}
|
|
132
|
-
else if (exp === undefined && act !== undefined) {
|
|
133
|
-
console.log(`Line ${lineIndex + 1}:`);
|
|
134
|
-
console.log(` + ${act}`);
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
console.log(`Line ${lineIndex + 1}:`);
|
|
138
|
-
console.log(` - ${exp}`);
|
|
139
|
-
console.log(` + ${act}`);
|
|
140
|
-
}
|
|
55
|
+
const outputResult = await runTestcaseTransform({
|
|
56
|
+
inputPath: basePath,
|
|
57
|
+
transformId: transform,
|
|
58
|
+
});
|
|
59
|
+
if (!outputResult.ok) {
|
|
60
|
+
console.error(`Error running transform: ${outputResult.error.message}`);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
const actualOutput = outputResult.value;
|
|
64
|
+
// Step 2: Write actual output
|
|
65
|
+
await fs.writeFile(actualPath, actualOutput);
|
|
66
|
+
console.log(`Created: test-cases/${testcase}/${transform}.js`);
|
|
67
|
+
// Step 3: Compare expected vs actual
|
|
68
|
+
const expectedContent = await fs.readFile(expectedPath, "utf8");
|
|
69
|
+
if (expectedContent === actualOutput) {
|
|
70
|
+
// Success! Delete expected file
|
|
71
|
+
await fs.unlink(expectedPath);
|
|
72
|
+
console.log(`\nSuccess: Output matches expected.`);
|
|
73
|
+
console.log(`Deleted: test-cases/${testcase}/${transform}-expected.js`);
|
|
74
|
+
console.log(`\nSnapshot ready: test-cases/${testcase}/${transform}.js`);
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
printSnapshotDiff({ expectedContent, actualOutput });
|
|
79
|
+
console.log(`\nExpected file preserved: test-cases/${testcase}/${transform}-expected.js`);
|
|
80
|
+
console.log("Fix the implementation or update the expected file, then re-run.");
|
|
81
|
+
return 1;
|
|
141
82
|
}
|
|
142
83
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
84
|
+
else {
|
|
85
|
+
const outputResult = await runTestcaseTransform({
|
|
86
|
+
inputPath: basePath,
|
|
87
|
+
transformId: transform,
|
|
88
|
+
});
|
|
89
|
+
if (!outputResult.ok) {
|
|
90
|
+
console.error(`Error running transform: ${outputResult.error.message}`);
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
const actualOutput = outputResult.value;
|
|
94
|
+
// Simple mode: just write actual output
|
|
95
|
+
await fs.writeFile(actualPath, actualOutput);
|
|
96
|
+
console.log(`Created: test-cases/${testcase}/${transform}.js`);
|
|
97
|
+
return 0;
|
|
146
98
|
}
|
|
147
|
-
console.log("---");
|
|
148
|
-
console.log(`\nExpected file preserved: test-cases/${testcase}/${transform}-expected.js`);
|
|
149
|
-
console.log("Fix the implementation or update the expected file, then re-run.");
|
|
150
|
-
return 1;
|
|
151
99
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type Result<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
value: T;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
error: Error;
|
|
7
|
+
};
|
|
8
|
+
type RunTestcaseTransformOptions = {
|
|
9
|
+
inputPath: string;
|
|
10
|
+
transformId: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const runTestcaseTransform: (options: RunTestcaseTransformOptions) => Promise<Result<string>>;
|
|
13
|
+
export {};
|