miniread 1.0.0 → 1.2.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/README.md +2 -2
- package/dist/scripts/evaluate/check-expected-evaluations.d.ts +17 -0
- package/dist/scripts/evaluate/check-expected-evaluations.js +35 -0
- package/dist/scripts/evaluate/check-snapshots.d.ts +14 -0
- package/dist/scripts/evaluate/check-snapshots.js +111 -0
- package/dist/scripts/evaluate/clone-metrics-report.d.ts +2 -0
- package/dist/scripts/evaluate/clone-metrics-report.js +6 -0
- package/dist/scripts/evaluate/create-evaluate-command.js +5 -20
- package/dist/scripts/evaluate/evaluate-help-text.d.ts +1 -0
- package/dist/scripts/evaluate/evaluate-help-text.js +21 -0
- package/dist/scripts/evaluate/pair-evaluator.d.ts +1 -0
- package/dist/scripts/evaluate/pair-evaluator.js +14 -3
- package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +2 -0
- package/dist/scripts/evaluate/parse-evaluate-cli-options.js +3 -1
- package/dist/scripts/evaluate/parse-transform-manifest.d.ts +2 -2
- package/dist/scripts/evaluate/parse-transform-manifest.js +2 -2
- package/dist/scripts/evaluate/relative-patch-path.d.ts +7 -0
- package/dist/scripts/evaluate/relative-patch-path.js +22 -0
- package/dist/scripts/evaluate/run-check-mode.d.ts +8 -0
- package/dist/scripts/evaluate/run-check-mode.js +116 -0
- package/dist/scripts/evaluate/run-evaluate-cli.js +11 -3
- package/dist/scripts/evaluate/run-evaluations.js +1 -0
- package/dist/scripts/evaluate/transform-content.d.ts +14 -0
- package/dist/scripts/evaluate/transform-content.js +31 -0
- package/dist/scripts/evaluate/transform-manifest.d.ts +0 -2
- package/dist/scripts/evaluate/transform-manifest.js +16 -35
- package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.d.ts +2 -0
- package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.js +91 -0
- package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.js +57 -0
- package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.d.ts +2 -0
- package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.js +34 -0
- package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.js +93 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.js +55 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.js +86 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.d.ts +2 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.js +41 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.js +93 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.js +64 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.js +91 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.d.ts +2 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.js +48 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.js +101 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.d.ts +3 -0
- package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.js +99 -0
- package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.d.ts +2 -0
- package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.js +117 -0
- package/dist/transforms/rename-binding/get-target-name.d.ts +4 -0
- package/dist/transforms/rename-binding/get-target-name.js +25 -0
- package/dist/transforms/rename-binding/is-valid-binding-identifier.d.ts +1 -0
- package/dist/transforms/rename-binding/is-valid-binding-identifier.js +10 -0
- package/dist/transforms/rename-destructured-aliases/binding-context.d.ts +7 -0
- package/dist/transforms/rename-destructured-aliases/binding-context.js +83 -0
- package/dist/transforms/rename-destructured-aliases/rename-destructured-aliases-transform.d.ts +2 -0
- package/dist/transforms/rename-destructured-aliases/rename-destructured-aliases-transform.js +118 -0
- package/dist/transforms/rename-promise-executor-parameters/is-valid-binding-identifier.d.ts +1 -0
- package/dist/transforms/rename-promise-executor-parameters/is-valid-binding-identifier.js +10 -0
- package/dist/transforms/rename-promise-executor-parameters/promise-executor-heuristics.d.ts +6 -0
- package/dist/transforms/rename-promise-executor-parameters/promise-executor-heuristics.js +88 -0
- package/dist/transforms/rename-promise-executor-parameters/rename-binding-if-safe.d.ts +3 -0
- package/dist/transforms/rename-promise-executor-parameters/rename-binding-if-safe.js +40 -0
- package/dist/transforms/rename-promise-executor-parameters/rename-promise-executor-parameters-transform.d.ts +2 -0
- package/dist/transforms/rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js +76 -0
- package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.d.ts +2 -0
- package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.js +86 -0
- package/dist/transforms/rename-use-reference-guards/get-member-expression-for-reference.d.ts +3 -0
- package/dist/transforms/rename-use-reference-guards/get-member-expression-for-reference.js +10 -0
- package/dist/transforms/rename-use-reference-guards/get-reference-usage.d.ts +9 -0
- package/dist/transforms/rename-use-reference-guards/get-reference-usage.js +47 -0
- package/dist/transforms/rename-use-reference-guards/get-target-name.d.ts +2 -0
- package/dist/transforms/rename-use-reference-guards/get-target-name.js +23 -0
- package/dist/transforms/rename-use-reference-guards/is-use-reference-false-initializer.d.ts +2 -0
- package/dist/transforms/rename-use-reference-guards/is-use-reference-false-initializer.js +25 -0
- package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.d.ts +1 -0
- package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.js +10 -0
- package/dist/transforms/rename-use-reference-guards/rename-use-reference-guards-transform.d.ts +3 -0
- package/dist/transforms/rename-use-reference-guards/rename-use-reference-guards-transform.js +55 -0
- package/dist/transforms/split-variable-declarations/create-variable-declaration.d.ts +2 -0
- package/dist/transforms/split-variable-declarations/create-variable-declaration.js +28 -0
- package/dist/transforms/split-variable-declarations/is-safe-to-split-variable-declaration.d.ts +3 -0
- package/dist/transforms/split-variable-declarations/is-safe-to-split-variable-declaration.js +52 -0
- package/dist/transforms/split-variable-declarations/split-variable-declarations-transform.d.ts +2 -0
- package/dist/transforms/split-variable-declarations/split-variable-declarations-transform.js +81 -0
- package/dist/transforms/transform-presets.js +2 -2
- package/dist/transforms/transform-registry.js +20 -4
- package/package.json +29 -23
- package/transform-manifest.json +110 -30
- package/dist/transforms/add-prefix/add-prefix-transform.d.ts +0 -2
- package/dist/transforms/add-prefix/add-prefix-transform.js +0 -40
- package/dist/transforms/add-suffix/add-suffix-transform.d.ts +0 -2
- package/dist/transforms/add-suffix/add-suffix-transform.js +0 -40
package/README.md
CHANGED
|
@@ -74,10 +74,10 @@ The `miniread-evaluate` CLI compares transform configurations by measuring diff
|
|
|
74
74
|
pnpm run miniread-evaluate -- --help
|
|
75
75
|
|
|
76
76
|
# Compare baseline none vs a transform
|
|
77
|
-
pnpm run miniread-evaluate -- --compare v1.0:v1.1 --baseline none --test
|
|
77
|
+
pnpm run miniread-evaluate -- --compare v1.0:v1.1 --baseline none --test rename-destructured-aliases
|
|
78
78
|
|
|
79
79
|
# Pipeline example: sort by reduction ratio (best first)
|
|
80
|
-
pnpm run miniread-evaluate -- --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test
|
|
80
|
+
pnpm run miniread-evaluate -- --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test rename-destructured-aliases \
|
|
81
81
|
| tail -n +2 | sort -k5,5nr | head -10
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SnapshotIssue } from "./check-snapshots.js";
|
|
2
|
+
type MissingManifestEntry = {
|
|
3
|
+
transformId: string;
|
|
4
|
+
path: string;
|
|
5
|
+
};
|
|
6
|
+
type CheckExpectedEvaluationsResult = {
|
|
7
|
+
transformIds: string[];
|
|
8
|
+
missingManifestEntries: MissingManifestEntry[];
|
|
9
|
+
snapshotIssues: SnapshotIssue[];
|
|
10
|
+
allPresent: boolean;
|
|
11
|
+
};
|
|
12
|
+
type CheckExpectedEvaluationsOptions = {
|
|
13
|
+
manifestPath: string;
|
|
14
|
+
testCasesDirectory: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const checkExpectedEvaluations: (options: CheckExpectedEvaluationsOptions) => Promise<CheckExpectedEvaluationsResult>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { allTransformIds } from "../../transforms/transform-registry.js";
|
|
3
|
+
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
4
|
+
import { parseTransformManifest } from "./parse-transform-manifest.js";
|
|
5
|
+
import { checkAllSnapshots } from "./check-snapshots.js";
|
|
6
|
+
export const checkExpectedEvaluations = async (options) => {
|
|
7
|
+
const { manifestPath, testCasesDirectory } = options;
|
|
8
|
+
const transformIds = allTransformIds.toSorted();
|
|
9
|
+
const missingManifestEntries = [];
|
|
10
|
+
// Check manifest entries
|
|
11
|
+
const manifestContent = await fs.readFile(manifestPath, "utf8");
|
|
12
|
+
const manifest = parseTransformManifest(manifestContent);
|
|
13
|
+
const manifestIds = new Set(manifest.transforms.map((t) => t.id));
|
|
14
|
+
for (const transformId of transformIds) {
|
|
15
|
+
if (!manifestIds.has(transformId)) {
|
|
16
|
+
missingManifestEntries.push({
|
|
17
|
+
transformId,
|
|
18
|
+
path: manifestPath,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Check test-cases matrix with smart snapshot detection
|
|
23
|
+
const snapshotIssues = await checkAllSnapshots({
|
|
24
|
+
testCasesDirectory,
|
|
25
|
+
transformIds,
|
|
26
|
+
transformRegistry,
|
|
27
|
+
});
|
|
28
|
+
const allPresent = missingManifestEntries.length === 0 && snapshotIssues.length === 0;
|
|
29
|
+
return {
|
|
30
|
+
transformIds,
|
|
31
|
+
missingManifestEntries,
|
|
32
|
+
snapshotIssues,
|
|
33
|
+
allPresent,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Transform } from "../../core/types.js";
|
|
2
|
+
export type SnapshotIssue = {
|
|
3
|
+
transformId: string;
|
|
4
|
+
testCase: string;
|
|
5
|
+
snapshotPath: string;
|
|
6
|
+
issue: "missing-required" | "unnecessary" | "outdated";
|
|
7
|
+
};
|
|
8
|
+
type CheckAllSnapshotsOptions = {
|
|
9
|
+
testCasesDirectory: string;
|
|
10
|
+
transformIds: string[];
|
|
11
|
+
transformRegistry: Record<string, Transform>;
|
|
12
|
+
};
|
|
13
|
+
export declare const checkAllSnapshots: (options: CheckAllSnapshotsOptions) => Promise<SnapshotIssue[]>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import * as prettier from "prettier";
|
|
4
|
+
import { transformContent } from "./transform-content.js";
|
|
5
|
+
const formatCode = async (code) => {
|
|
6
|
+
return prettier.format(code, { parser: "babel" });
|
|
7
|
+
};
|
|
8
|
+
const fileExists = async (filePath) => {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(filePath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const checkSnapshot = async (options) => {
|
|
18
|
+
const { basePath, baseContent, formattedBase, snapshotPath, transformId, testCaseName, transform, } = options;
|
|
19
|
+
const snapshotExists = await fileExists(snapshotPath);
|
|
20
|
+
if (snapshotExists) {
|
|
21
|
+
const snapshotContent = await fs.readFile(snapshotPath, "utf8");
|
|
22
|
+
const formattedSnapshot = await formatCode(snapshotContent);
|
|
23
|
+
// Check if snapshot is unnecessary (formatted snapshot equals formatted base)
|
|
24
|
+
if (formattedSnapshot === formattedBase) {
|
|
25
|
+
return {
|
|
26
|
+
transformId,
|
|
27
|
+
testCase: testCaseName,
|
|
28
|
+
snapshotPath,
|
|
29
|
+
issue: "unnecessary",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (transform) {
|
|
33
|
+
// Check if snapshot is outdated (formatted snapshot differs from current transform output)
|
|
34
|
+
const result = await transformContent({
|
|
35
|
+
content: baseContent,
|
|
36
|
+
filePath: basePath,
|
|
37
|
+
transform,
|
|
38
|
+
});
|
|
39
|
+
if (result.ok) {
|
|
40
|
+
const formattedOutput = await formatCode(result.output);
|
|
41
|
+
if (formattedOutput !== formattedSnapshot) {
|
|
42
|
+
return {
|
|
43
|
+
transformId,
|
|
44
|
+
testCase: testCaseName,
|
|
45
|
+
snapshotPath,
|
|
46
|
+
issue: "outdated",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (transform) {
|
|
53
|
+
// Check if snapshot is required (formatted transform output differs from formatted base)
|
|
54
|
+
const result = await transformContent({
|
|
55
|
+
content: baseContent,
|
|
56
|
+
filePath: basePath,
|
|
57
|
+
transform,
|
|
58
|
+
});
|
|
59
|
+
if (result.ok) {
|
|
60
|
+
const formattedOutput = await formatCode(result.output);
|
|
61
|
+
if (formattedOutput !== formattedBase) {
|
|
62
|
+
return {
|
|
63
|
+
transformId,
|
|
64
|
+
testCase: testCaseName,
|
|
65
|
+
snapshotPath,
|
|
66
|
+
issue: "missing-required",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
};
|
|
73
|
+
const getTestCaseNames = async (testCasesDirectory) => {
|
|
74
|
+
const entries = await fs.readdir(testCasesDirectory, { withFileTypes: true });
|
|
75
|
+
const testCaseNames = [];
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (!entry.isDirectory())
|
|
78
|
+
continue;
|
|
79
|
+
const basePath = path.join(testCasesDirectory, entry.name, "base.js");
|
|
80
|
+
if (await fileExists(basePath)) {
|
|
81
|
+
testCaseNames.push(entry.name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return testCaseNames.toSorted();
|
|
85
|
+
};
|
|
86
|
+
export const checkAllSnapshots = async (options) => {
|
|
87
|
+
const { testCasesDirectory, transformIds, transformRegistry } = options;
|
|
88
|
+
const snapshotIssues = [];
|
|
89
|
+
const testCaseNames = await getTestCaseNames(testCasesDirectory);
|
|
90
|
+
for (const testCaseName of testCaseNames) {
|
|
91
|
+
const basePath = path.join(testCasesDirectory, testCaseName, "base.js");
|
|
92
|
+
const baseContent = await fs.readFile(basePath, "utf8");
|
|
93
|
+
const formattedBase = await formatCode(baseContent);
|
|
94
|
+
for (const transformId of transformIds) {
|
|
95
|
+
const snapshotPath = path.join(testCasesDirectory, testCaseName, `${transformId}.js`);
|
|
96
|
+
const issue = await checkSnapshot({
|
|
97
|
+
basePath,
|
|
98
|
+
baseContent,
|
|
99
|
+
formattedBase,
|
|
100
|
+
snapshotPath,
|
|
101
|
+
transformId,
|
|
102
|
+
testCaseName,
|
|
103
|
+
transform: transformRegistry[transformId],
|
|
104
|
+
});
|
|
105
|
+
if (issue) {
|
|
106
|
+
snapshotIssues.push(issue);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return snapshotIssues;
|
|
111
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command, InvalidArgumentError } from "@commander-js/extra-typings";
|
|
2
|
+
import { evaluateHelpText } from "./evaluate-help-text.js";
|
|
2
3
|
const parseOutputFormat = (value) => {
|
|
3
4
|
if (value === "tsv" || value === "json")
|
|
4
5
|
return value;
|
|
@@ -21,27 +22,11 @@ export const createEvaluateCommand = (options) => {
|
|
|
21
22
|
.option("--write-metrics", "Write metrics JSON to the evaluation directory")
|
|
22
23
|
.option("--metrics-file <path>", "Write metrics JSON to a specific path")
|
|
23
24
|
.option("--write-patches", "Write baseline/test diff patches to the evaluation directory")
|
|
24
|
-
.option("-u, --update-manifest", "Update transform-manifest.json
|
|
25
|
+
.option("-u, --update-manifest", "Update transform-manifest.json metrics (does not add new entries or edit notes)")
|
|
25
26
|
.option("--manifest-file <path>", "Path to transform-manifest.json", "transform-manifest.json")
|
|
26
27
|
.option("-f, --overwrite", "Overwrite existing metrics/patch files")
|
|
27
|
-
.option("--no-format-code", "Skip formatting temp directories with Prettier before diffing")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- diff (override: MINIREAD_EVALUATE_DIFF_PATH)
|
|
31
|
-
- pnpm + prettier (used unless --no-format-code; overrides: MINIREAD_EVALUATE_PNPM_PATH, MINIREAD_EVALUATE_PRETTIER_PATH)
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
# Compare baseline none vs a transform
|
|
35
|
-
miniread-evaluate --compare v1.0:v1.1 --baseline none --test add-prefix
|
|
36
|
-
|
|
37
|
-
# Multiple pairs, output JSON and inspect with jq
|
|
38
|
-
miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test add-prefix --format json | jq '.results[] | {pair, reductionRatio}'
|
|
39
|
-
|
|
40
|
-
# Pipeline example: sort by reduction ratio (best first)
|
|
41
|
-
miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test add-prefix | tail -n +2 | sort -k5,5nr | head -10
|
|
42
|
-
|
|
43
|
-
# Write patches + metrics (refuses to overwrite unless -f/--overwrite)
|
|
44
|
-
miniread-evaluate --compare v1.0:v1.1 --baseline none --test add-prefix --write-patches --write-metrics
|
|
45
|
-
`);
|
|
28
|
+
.option("--no-format-code", "Skip formatting temp directories with Prettier before diffing")
|
|
29
|
+
.option("--check", "Check that all expected evaluation metrics files exist (does not run evaluations)");
|
|
30
|
+
program.addHelpText("after", evaluateHelpText);
|
|
46
31
|
return program;
|
|
47
32
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const evaluateHelpText = "\nDependencies:\n - diff (override: MINIREAD_EVALUATE_DIFF_PATH)\n - pnpm + prettier (used unless --no-format-code; overrides: MINIREAD_EVALUATE_PNPM_PATH, MINIREAD_EVALUATE_PRETTIER_PATH)\n\nExamples:\n # Compare baseline none vs a transform\n miniread-evaluate --compare v1.0:v1.1 --baseline none --test rename-destructured-aliases\n\n # Multiple pairs, output JSON and inspect with jq\n miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test rename-destructured-aliases --format json | jq '.results[] | {pair, reductionRatio}'\n\n # Pipeline example: sort by reduction ratio (best first)\n miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test rename-destructured-aliases | tail -n +2 | sort -k5,5nr | head -10\n\n # Write patches + metrics (refuses to overwrite unless -f/--overwrite)\n miniread-evaluate --compare v1.0:v1.1 --baseline none --test rename-destructured-aliases --write-patches --write-metrics\n\n # Check that all expected evaluation metrics files exist\n miniread-evaluate --check\n";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const evaluateHelpText = `
|
|
2
|
+
Dependencies:
|
|
3
|
+
- diff (override: MINIREAD_EVALUATE_DIFF_PATH)
|
|
4
|
+
- pnpm + prettier (used unless --no-format-code; overrides: MINIREAD_EVALUATE_PNPM_PATH, MINIREAD_EVALUATE_PRETTIER_PATH)
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
# Compare baseline none vs a transform
|
|
8
|
+
miniread-evaluate --compare v1.0:v1.1 --baseline none --test rename-destructured-aliases
|
|
9
|
+
|
|
10
|
+
# Multiple pairs, output JSON and inspect with jq
|
|
11
|
+
miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test rename-destructured-aliases --format json | jq '.results[] | {pair, reductionRatio}'
|
|
12
|
+
|
|
13
|
+
# Pipeline example: sort by reduction ratio (best first)
|
|
14
|
+
miniread-evaluate --compare v1.0:v1.1 --compare v2.0:v2.1 --baseline none --test rename-destructured-aliases | tail -n +2 | sort -k5,5nr | head -10
|
|
15
|
+
|
|
16
|
+
# Write patches + metrics (refuses to overwrite unless -f/--overwrite)
|
|
17
|
+
miniread-evaluate --compare v1.0:v1.1 --baseline none --test rename-destructured-aliases --write-patches --write-metrics
|
|
18
|
+
|
|
19
|
+
# Check that all expected evaluation metrics files exist
|
|
20
|
+
miniread-evaluate --check
|
|
21
|
+
`;
|
|
@@ -3,11 +3,12 @@ import path from "node:path";
|
|
|
3
3
|
import envPaths from "env-paths";
|
|
4
4
|
import { computeDirectoryDiff } from "./diff-utilities.js";
|
|
5
5
|
import { calculateReductionRatio, formatDiffStats } from "./metrics.js";
|
|
6
|
+
import { getRelativePatchPath } from "./relative-patch-path.js";
|
|
6
7
|
import { formatDirectoryWithPrettier } from "./shell-utilities.js";
|
|
7
8
|
import { runPairTransformations } from "./run-pair-transformations.js";
|
|
8
9
|
const appPaths = envPaths("miniread");
|
|
9
10
|
export const evaluatePair = async (options) => {
|
|
10
|
-
const { pair, baselineTransforms, testTransforms, sourcesDirectory, rootDirectory, nodePath, minireadCliPath, diffPath, pnpmPath, prettierPath, formatCode, baselinePatchPath, testPatchPath, overwrite, verbose, log, } = options;
|
|
11
|
+
const { pair, baselineTransforms, testTransforms, sourcesDirectory, rootDirectory, evaluationDirectory, nodePath, minireadCliPath, diffPath, pnpmPath, prettierPath, formatCode, baselinePatchPath, testPatchPath, overwrite, verbose, log, } = options;
|
|
11
12
|
if (verbose) {
|
|
12
13
|
log(`Evaluating: ${pair.from} -> ${pair.to}`);
|
|
13
14
|
}
|
|
@@ -86,6 +87,16 @@ export const evaluatePair = async (options) => {
|
|
|
86
87
|
const status = reductionRatio >= 0 ? "GOOD" : "BAD";
|
|
87
88
|
if (verbose)
|
|
88
89
|
log(`Reduction ratio: ${percentReduction}% (${status})`);
|
|
90
|
+
const reportBaselinePatchPath = getRelativePatchPath({
|
|
91
|
+
rootDirectory,
|
|
92
|
+
evaluationDirectory,
|
|
93
|
+
patchPath: baselinePatchPath,
|
|
94
|
+
});
|
|
95
|
+
const reportTestPatchPath = getRelativePatchPath({
|
|
96
|
+
rootDirectory,
|
|
97
|
+
evaluationDirectory,
|
|
98
|
+
patchPath: testPatchPath,
|
|
99
|
+
});
|
|
89
100
|
return {
|
|
90
101
|
timestamp: new Date().toISOString(),
|
|
91
102
|
pair,
|
|
@@ -94,8 +105,8 @@ export const evaluatePair = async (options) => {
|
|
|
94
105
|
baselineDiff,
|
|
95
106
|
testDiff,
|
|
96
107
|
reductionRatio,
|
|
97
|
-
baselinePatchPath,
|
|
98
|
-
testPatchPath,
|
|
108
|
+
baselinePatchPath: reportBaselinePatchPath,
|
|
109
|
+
testPatchPath: reportTestPatchPath,
|
|
99
110
|
};
|
|
100
111
|
}
|
|
101
112
|
finally {
|
|
@@ -15,9 +15,11 @@ export type EvaluateCliRawOptions = {
|
|
|
15
15
|
manifestFile: string;
|
|
16
16
|
overwrite?: boolean;
|
|
17
17
|
formatCode: boolean;
|
|
18
|
+
check?: boolean;
|
|
18
19
|
};
|
|
19
20
|
export type EvaluateCliOptions = {
|
|
20
21
|
verbose: boolean;
|
|
22
|
+
checkMode: boolean;
|
|
21
23
|
pairs: ComparisonPair[];
|
|
22
24
|
baselineTransforms: string[];
|
|
23
25
|
testTransforms: string[];
|
|
@@ -53,7 +53,8 @@ const parseComparePair = (value) => {
|
|
|
53
53
|
};
|
|
54
54
|
export const parseEvaluateCliOptions = (options) => {
|
|
55
55
|
const { cwd, rawOptions } = options;
|
|
56
|
-
|
|
56
|
+
const checkMode = rawOptions.check ?? false;
|
|
57
|
+
if (!checkMode && rawOptions.compare.length === 0) {
|
|
57
58
|
throw new Error("at least one --compare flag is required");
|
|
58
59
|
}
|
|
59
60
|
const pairs = [];
|
|
@@ -78,6 +79,7 @@ export const parseEvaluateCliOptions = (options) => {
|
|
|
78
79
|
: path.join(evaluationDirectory, `metrics-${baselineSlug}-vs-${testSlug}.json`);
|
|
79
80
|
return {
|
|
80
81
|
verbose,
|
|
82
|
+
checkMode,
|
|
81
83
|
pairs,
|
|
82
84
|
baselineTransforms,
|
|
83
85
|
testTransforms,
|
|
@@ -2,11 +2,11 @@ import type { Transform } from "../../core/types.js";
|
|
|
2
2
|
type TransformManifestEntry = {
|
|
3
3
|
id: string;
|
|
4
4
|
description: string;
|
|
5
|
-
iteration: number;
|
|
6
5
|
scope: Transform["scope"];
|
|
7
6
|
parallelizable: boolean;
|
|
8
7
|
diffReductionImpact: number;
|
|
9
|
-
|
|
8
|
+
recommended: boolean;
|
|
9
|
+
evaluatedAt?: string;
|
|
10
10
|
notes?: string;
|
|
11
11
|
supersededBy?: string;
|
|
12
12
|
};
|
|
@@ -3,11 +3,11 @@ const TransformManifestEntrySchema = z
|
|
|
3
3
|
.object({
|
|
4
4
|
id: z.string().min(1),
|
|
5
5
|
description: z.string(),
|
|
6
|
-
iteration: z.number().int().nonnegative(),
|
|
7
6
|
scope: z.union([z.literal("file"), z.literal("project")]),
|
|
8
7
|
parallelizable: z.boolean(),
|
|
9
8
|
diffReductionImpact: z.number(),
|
|
10
|
-
|
|
9
|
+
recommended: z.boolean(),
|
|
10
|
+
evaluatedAt: z.iso.datetime().optional(),
|
|
11
11
|
notes: z.string().optional(),
|
|
12
12
|
supersededBy: z.string().min(1).optional(),
|
|
13
13
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export const getRelativePatchPath = (options) => {
|
|
3
|
+
const { rootDirectory, evaluationDirectory, patchPath } = options;
|
|
4
|
+
if (!patchPath)
|
|
5
|
+
return;
|
|
6
|
+
const relativePath = path.relative(rootDirectory, patchPath);
|
|
7
|
+
if (relativePath === "" || relativePath === ".")
|
|
8
|
+
return;
|
|
9
|
+
// Avoid leaking paths outside the repo when evaluation output is elsewhere.
|
|
10
|
+
if (!path.isAbsolute(relativePath) &&
|
|
11
|
+
relativePath !== ".." &&
|
|
12
|
+
!relativePath.startsWith(`..${path.sep}`)) {
|
|
13
|
+
return relativePath;
|
|
14
|
+
}
|
|
15
|
+
const evaluationRelativePath = path.relative(evaluationDirectory, patchPath);
|
|
16
|
+
if (!path.isAbsolute(evaluationRelativePath) &&
|
|
17
|
+
evaluationRelativePath !== ".." &&
|
|
18
|
+
!evaluationRelativePath.startsWith(`..${path.sep}`)) {
|
|
19
|
+
return evaluationRelativePath;
|
|
20
|
+
}
|
|
21
|
+
return path.basename(patchPath);
|
|
22
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OutputFormat } from "./create-evaluate-command.js";
|
|
2
|
+
type RunCheckModeOptions = {
|
|
3
|
+
manifestPath: string;
|
|
4
|
+
rootDirectory: string;
|
|
5
|
+
format: OutputFormat;
|
|
6
|
+
};
|
|
7
|
+
export declare const runCheckMode: (options: RunCheckModeOptions) => Promise<number>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { checkExpectedEvaluations } from "./check-expected-evaluations.js";
|
|
3
|
+
export const runCheckMode = async (options) => {
|
|
4
|
+
const { manifestPath, rootDirectory, format } = options;
|
|
5
|
+
const checkResult = await checkExpectedEvaluations({
|
|
6
|
+
manifestPath,
|
|
7
|
+
testCasesDirectory: path.join(rootDirectory, "test-cases"),
|
|
8
|
+
});
|
|
9
|
+
if (format === "json") {
|
|
10
|
+
console.log(JSON.stringify(checkResult, undefined, 2));
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const allIssues = [
|
|
14
|
+
...checkResult.missingManifestEntries.map((item) => ({
|
|
15
|
+
type: "manifest",
|
|
16
|
+
transformId: item.transformId,
|
|
17
|
+
testCase: "",
|
|
18
|
+
path: item.path,
|
|
19
|
+
})),
|
|
20
|
+
...checkResult.snapshotIssues.map((item) => ({
|
|
21
|
+
type: item.issue,
|
|
22
|
+
transformId: item.transformId,
|
|
23
|
+
testCase: item.testCase,
|
|
24
|
+
path: item.snapshotPath,
|
|
25
|
+
})),
|
|
26
|
+
];
|
|
27
|
+
if (allIssues.length > 0) {
|
|
28
|
+
console.log("type\ttransform_id\ttest_case\tpath");
|
|
29
|
+
for (const item of allIssues) {
|
|
30
|
+
console.log(`${item.type}\t${item.transformId}\t${item.testCase}\t${item.path}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let exitCode = 0;
|
|
35
|
+
if (checkResult.missingManifestEntries.length > 0) {
|
|
36
|
+
console.error(`\n[MISSING MANIFEST ENTRIES] ${checkResult.missingManifestEntries.length} transform(s) not in transform-manifest.json:`);
|
|
37
|
+
for (const item of checkResult.missingManifestEntries) {
|
|
38
|
+
console.error(` - ${item.transformId}`);
|
|
39
|
+
}
|
|
40
|
+
console.error(`\n To fix: Add entries manually to transform-manifest.json, or run evaluation with --update-manifest`);
|
|
41
|
+
exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
const missingSnapshots = checkResult.snapshotIssues.filter((issue) => issue.issue === "missing-required");
|
|
44
|
+
const unnecessarySnapshots = checkResult.snapshotIssues.filter((issue) => issue.issue === "unnecessary");
|
|
45
|
+
const outdatedSnapshots = checkResult.snapshotIssues.filter((issue) => issue.issue === "outdated");
|
|
46
|
+
if (missingSnapshots.length > 0) {
|
|
47
|
+
console.error(`\n[MISSING SNAPSHOTS] ${missingSnapshots.length} snapshot(s) required but missing:`);
|
|
48
|
+
const byTransform = new Map();
|
|
49
|
+
for (const item of missingSnapshots) {
|
|
50
|
+
const testCases = byTransform.get(item.transformId);
|
|
51
|
+
if (testCases) {
|
|
52
|
+
testCases.push(item.testCase);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
byTransform.set(item.transformId, [item.testCase]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const [transformId, testCases] of byTransform) {
|
|
59
|
+
console.error(` - ${transformId}: ${testCases.join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
console.error(`\n To fix: Generate snapshots for each transform:`);
|
|
62
|
+
console.error(` for testcase in test-cases/*/; do`);
|
|
63
|
+
console.error(` cat "\${testcase}base.js" | pnpm -s run miniread -- --transforms <transform-id> \\`);
|
|
64
|
+
console.error(` | pnpm exec prettier --stdin-filepath test.js --parser babel \\`);
|
|
65
|
+
console.error(` > "\${testcase}<transform-id>.js"`);
|
|
66
|
+
console.error(` done`);
|
|
67
|
+
exitCode = 1;
|
|
68
|
+
}
|
|
69
|
+
if (unnecessarySnapshots.length > 0) {
|
|
70
|
+
console.error(`\n[UNNECESSARY SNAPSHOTS] ${unnecessarySnapshots.length} snapshot(s) identical to base.js (should be removed):`);
|
|
71
|
+
const byTransform = new Map();
|
|
72
|
+
for (const item of unnecessarySnapshots) {
|
|
73
|
+
const testCases = byTransform.get(item.transformId);
|
|
74
|
+
if (testCases) {
|
|
75
|
+
testCases.push(item.testCase);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
byTransform.set(item.transformId, [item.testCase]);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const [transformId, testCases] of byTransform) {
|
|
82
|
+
console.error(` - ${transformId}: ${testCases.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
console.error(`\n To fix: Remove unnecessary snapshots:`);
|
|
85
|
+
for (const item of unnecessarySnapshots) {
|
|
86
|
+
console.error(` rm "${item.snapshotPath}"`);
|
|
87
|
+
}
|
|
88
|
+
exitCode = 1;
|
|
89
|
+
}
|
|
90
|
+
if (outdatedSnapshots.length > 0) {
|
|
91
|
+
console.error(`\n[OUTDATED SNAPSHOTS] ${outdatedSnapshots.length} snapshot(s) don't match current transform output:`);
|
|
92
|
+
const byTransform = new Map();
|
|
93
|
+
for (const item of outdatedSnapshots) {
|
|
94
|
+
const testCases = byTransform.get(item.transformId);
|
|
95
|
+
if (testCases) {
|
|
96
|
+
testCases.push(item.testCase);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
byTransform.set(item.transformId, [item.testCase]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const [transformId, testCases] of byTransform) {
|
|
103
|
+
console.error(` - ${transformId}: ${testCases.join(", ")}`);
|
|
104
|
+
}
|
|
105
|
+
console.error(`\n To fix: Regenerate outdated snapshots:`);
|
|
106
|
+
for (const item of outdatedSnapshots) {
|
|
107
|
+
const testCaseDirectory = path.dirname(item.snapshotPath);
|
|
108
|
+
console.error(` cat "${testCaseDirectory}/base.js" | pnpm -s run miniread -- --transforms ${item.transformId} | pnpm exec prettier --stdin-filepath test.js --parser babel > "${item.snapshotPath}"`);
|
|
109
|
+
}
|
|
110
|
+
exitCode = 1;
|
|
111
|
+
}
|
|
112
|
+
if (exitCode === 0) {
|
|
113
|
+
console.error(`\nAll checks passed: ${checkResult.transformIds.length} transforms verified.`);
|
|
114
|
+
}
|
|
115
|
+
return exitCode;
|
|
116
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
3
|
-
import { transformRegistry } from "../../transforms/transform-registry.js";
|
|
4
3
|
import { createEvaluateCommand } from "./create-evaluate-command.js";
|
|
5
4
|
import { parseEvaluateCliOptions, } from "./parse-evaluate-cli-options.js";
|
|
6
5
|
import { resolveEvaluateDependencies } from "./resolve-evaluate-dependencies.js";
|
|
6
|
+
import { runCheckMode } from "./run-check-mode.js";
|
|
7
7
|
import { runEvaluations } from "./run-evaluations.js";
|
|
8
8
|
import { writeEvaluationStdout } from "./write-evaluation-stdout.js";
|
|
9
9
|
import { updateTransformManifestFromEvaluation } from "./transform-manifest.js";
|
|
10
|
+
import { cloneMetricsReport } from "./clone-metrics-report.js";
|
|
10
11
|
import { writeJsonFileAtomic } from "./write-text-file-atomic.js";
|
|
11
12
|
export const runEvaluateCli = async (argv) => {
|
|
12
13
|
const program = createEvaluateCommand({ version: packageJson.version });
|
|
@@ -32,6 +33,13 @@ export const runEvaluateCli = async (argv) => {
|
|
|
32
33
|
return;
|
|
33
34
|
console.error(message);
|
|
34
35
|
};
|
|
36
|
+
if (parsedOptions.checkMode) {
|
|
37
|
+
return runCheckMode({
|
|
38
|
+
manifestPath: parsedOptions.manifestPath,
|
|
39
|
+
rootDirectory: process.cwd(),
|
|
40
|
+
format: parsedOptions.format,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
35
43
|
let dependencies;
|
|
36
44
|
try {
|
|
37
45
|
dependencies = resolveEvaluateDependencies({
|
|
@@ -78,7 +86,8 @@ export const runEvaluateCli = async (argv) => {
|
|
|
78
86
|
});
|
|
79
87
|
if (parsedOptions.shouldWriteMetrics) {
|
|
80
88
|
try {
|
|
81
|
-
|
|
89
|
+
const sanitizedReport = cloneMetricsReport(report);
|
|
90
|
+
await writeJsonFileAtomic(parsedOptions.metricsPath, sanitizedReport, {
|
|
82
91
|
overwrite: parsedOptions.overwrite,
|
|
83
92
|
});
|
|
84
93
|
log(`Wrote ${parsedOptions.metricsPath}`);
|
|
@@ -100,7 +109,6 @@ export const runEvaluateCli = async (argv) => {
|
|
|
100
109
|
baselineTransforms: parsedOptions.baselineTransforms,
|
|
101
110
|
testTransforms: parsedOptions.testTransforms,
|
|
102
111
|
reductionRatios: results.map((r) => r.reductionRatio),
|
|
103
|
-
transformRegistry,
|
|
104
112
|
});
|
|
105
113
|
console.error(updateResult.message);
|
|
106
114
|
}
|
|
@@ -23,6 +23,7 @@ export const runEvaluations = async (options) => {
|
|
|
23
23
|
testTransforms,
|
|
24
24
|
sourcesDirectory,
|
|
25
25
|
rootDirectory,
|
|
26
|
+
evaluationDirectory,
|
|
26
27
|
nodePath: process.execPath,
|
|
27
28
|
minireadCliPath: process.env.MINIREAD_EVALUATE_MINIREAD_PATH ?? "bin/miniread",
|
|
28
29
|
diffPath: dependencies.diffPath,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Transform } from "../../core/types.js";
|
|
2
|
+
type TransformContentResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
output: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
export declare const transformContent: (options: {
|
|
10
|
+
content: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
transform: Transform;
|
|
13
|
+
}) => Promise<TransformContentResult>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buildProjectGraph } from "../../core/project-graph.js";
|
|
2
|
+
import { generateCode } from "../../cli/generate-code.js";
|
|
3
|
+
export const transformContent = async (options) => {
|
|
4
|
+
const { content, filePath, transform } = options;
|
|
5
|
+
const projectGraph = buildProjectGraph([{ path: filePath, content }]);
|
|
6
|
+
const file = projectGraph.files.get(filePath);
|
|
7
|
+
if (!file) {
|
|
8
|
+
return { ok: false, error: "Failed to parse input" };
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
await transform.transform({
|
|
12
|
+
projectGraph,
|
|
13
|
+
currentFile: undefined,
|
|
14
|
+
options: {},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
+
return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: `Transform '${transform.id}' failed: ${message}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const generated = generateCode(file.ast);
|
|
25
|
+
const output = generated === ""
|
|
26
|
+
? ""
|
|
27
|
+
: generated.endsWith("\n")
|
|
28
|
+
? generated
|
|
29
|
+
: `${generated}\n`;
|
|
30
|
+
return { ok: true, output };
|
|
31
|
+
};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { Transform } from "../../core/types.js";
|
|
2
1
|
type UpdateTransformManifestOptions = {
|
|
3
2
|
manifestPath: string;
|
|
4
3
|
baselineTransforms: string[];
|
|
5
4
|
testTransforms: string[];
|
|
6
5
|
reductionRatios: number[];
|
|
7
|
-
transformRegistry: Record<string, Transform>;
|
|
8
6
|
};
|
|
9
7
|
export declare const updateTransformManifestFromEvaluation: (options: UpdateTransformManifestOptions) => Promise<{
|
|
10
8
|
updated: boolean;
|