miniread 1.5.0 → 1.7.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.
Files changed (69) hide show
  1. package/dist/core/normalize-code.d.ts +17 -0
  2. package/dist/core/normalize-code.js +35 -0
  3. package/dist/core/stable-naming.d.ts +61 -0
  4. package/dist/core/stable-naming.js +121 -0
  5. package/dist/scripts/evaluate/check-expected-evaluations.js +2 -0
  6. package/dist/scripts/evaluate/check-recommended-snapshot.d.ts +11 -0
  7. package/dist/scripts/evaluate/check-recommended-snapshot.js +53 -0
  8. package/dist/scripts/evaluate/check-snapshots.d.ts +1 -0
  9. package/dist/scripts/evaluate/check-snapshots.js +21 -2
  10. package/dist/scripts/evaluate/transform-content.d.ts +5 -0
  11. package/dist/scripts/evaluate/transform-content.js +23 -13
  12. package/dist/transforms/rename-catch-parameters/rename-catch-parameters-transform.js +15 -38
  13. package/dist/transforms/rename-destructured-aliases/rename-destructured-aliases-transform.js +13 -12
  14. package/dist/transforms/rename-event-parameters/process-event-handler-function.d.ts +2 -3
  15. package/dist/transforms/rename-event-parameters/process-event-handler-function.js +13 -9
  16. package/dist/transforms/rename-event-parameters/rename-event-parameters-transform.d.ts +1 -1
  17. package/dist/transforms/rename-event-parameters/rename-event-parameters-transform.js +10 -7
  18. package/dist/transforms/rename-loop-index-variables/rename-loop-index-variables-transform.js +19 -13
  19. package/dist/transforms/rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js +12 -5
  20. package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.d.ts +1 -1
  21. package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.js +10 -11
  22. package/dist/transforms/rename-use-reference-guards/rename-use-reference-guards-transform.js +10 -11
  23. package/dist/transforms/rename-use-reference-guards-v2/get-member-expression-for-reference.d.ts +3 -0
  24. package/dist/transforms/rename-use-reference-guards-v2/get-member-expression-for-reference.js +10 -0
  25. package/dist/transforms/rename-use-reference-guards-v2/get-reference-usage.d.ts +8 -0
  26. package/dist/transforms/rename-use-reference-guards-v2/get-reference-usage.js +58 -0
  27. package/dist/transforms/rename-use-reference-guards-v2/is-use-reference-false-initializer.d.ts +2 -0
  28. package/dist/transforms/rename-use-reference-guards-v2/is-use-reference-false-initializer.js +40 -0
  29. package/dist/transforms/rename-use-reference-guards-v2/rename-use-reference-guards-v2-transform.d.ts +3 -0
  30. package/dist/transforms/rename-use-reference-guards-v2/rename-use-reference-guards-v2-transform.js +54 -0
  31. package/dist/transforms/transform-registry.js +2 -10
  32. package/package.json +1 -1
  33. package/transform-manifest.json +15 -55
  34. package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.d.ts +0 -2
  35. package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.js +0 -91
  36. package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.d.ts +0 -3
  37. package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.js +0 -57
  38. package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.d.ts +0 -2
  39. package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.js +0 -34
  40. package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.d.ts +0 -3
  41. package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.js +0 -93
  42. package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.d.ts +0 -3
  43. package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.js +0 -55
  44. package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.d.ts +0 -3
  45. package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.js +0 -86
  46. package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.d.ts +0 -2
  47. package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.js +0 -41
  48. package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.d.ts +0 -3
  49. package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.js +0 -93
  50. package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.d.ts +0 -3
  51. package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.js +0 -64
  52. package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.d.ts +0 -3
  53. package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.js +0 -91
  54. package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.d.ts +0 -2
  55. package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.js +0 -48
  56. package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.d.ts +0 -3
  57. package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.js +0 -101
  58. package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.d.ts +0 -3
  59. package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.js +0 -99
  60. package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.d.ts +0 -2
  61. package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.js +0 -117
  62. package/dist/transforms/rename-binding/get-target-name.d.ts +0 -4
  63. package/dist/transforms/rename-binding/get-target-name.js +0 -25
  64. package/dist/transforms/rename-binding/is-valid-binding-identifier.d.ts +0 -1
  65. package/dist/transforms/rename-binding/is-valid-binding-identifier.js +0 -10
  66. package/dist/transforms/rename-use-reference-guards/get-target-name.d.ts +0 -2
  67. package/dist/transforms/rename-use-reference-guards/get-target-name.js +0 -23
  68. package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.d.ts +0 -1
  69. package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.js +0 -10
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Normalizes JavaScript/TypeScript code through the parse → generate → format pipeline.
3
+ *
4
+ * This ensures consistent whitespace and formatting regardless of the original source.
5
+ * Use this when comparing code that may have gone through different pipelines
6
+ * (e.g., comparing base.js to transform output).
7
+ *
8
+ * The pipeline:
9
+ * 1. Parse with Babel (normalizes AST structure)
10
+ * 2. Generate with Babel (produces consistent whitespace)
11
+ * 3. Format with Prettier (final formatting)
12
+ */
13
+ /**
14
+ * Normalizes code through parse → generate → format pipeline.
15
+ * Returns the normalized code string.
16
+ */
17
+ export declare const normalizeCode: (code: string) => Promise<string>;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Normalizes JavaScript/TypeScript code through the parse → generate → format pipeline.
3
+ *
4
+ * This ensures consistent whitespace and formatting regardless of the original source.
5
+ * Use this when comparing code that may have gone through different pipelines
6
+ * (e.g., comparing base.js to transform output).
7
+ *
8
+ * The pipeline:
9
+ * 1. Parse with Babel (normalizes AST structure)
10
+ * 2. Generate with Babel (produces consistent whitespace)
11
+ * 3. Format with Prettier (final formatting)
12
+ */
13
+ import { createRequire } from "node:module";
14
+ import * as prettier from "prettier";
15
+ const require = createRequire(import.meta.url);
16
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
17
+ const parser = require("@babel/parser");
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
19
+ const parse = parser.parse;
20
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
21
+ const generate = require("@babel/generator").default;
22
+ const PARSER_OPTIONS = {
23
+ sourceType: "unambiguous",
24
+ plugins: ["jsx", "typescript", "importAttributes"],
25
+ };
26
+ /**
27
+ * Normalizes code through parse → generate → format pipeline.
28
+ * Returns the normalized code string.
29
+ */
30
+ export const normalizeCode = async (code) => {
31
+ const ast = parse(code, PARSER_OPTIONS);
32
+ const generated = generate(ast);
33
+ const formatted = await prettier.format(generated.code, { parser: "babel" });
34
+ return formatted;
35
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Helpers for the stable name prefix convention (§5.6-5.9 in workflow.md).
3
+ *
4
+ * The `$` prefix signals that a variable has been renamed stably by a transform
5
+ * and should be skipped by other transforms.
6
+ *
7
+ * - Stable names (`$timeoutId`): Readable AND deterministic across versions
8
+ * - Readable names (`timeoutId`): Semantic but order-dependent
9
+ */
10
+ import type { Scope } from "@babel/traverse";
11
+ /**
12
+ * Returns true if the name has been stable-renamed (starts with $).
13
+ * Transforms should skip variables with this prefix.
14
+ */
15
+ export declare const isStableRenamed: (name: string) => boolean;
16
+ /**
17
+ * Entry for a planned rename operation.
18
+ */
19
+ type RenameEntry = {
20
+ /** The Babel scope containing the binding */
21
+ scope: Scope;
22
+ /** Current name of the variable */
23
+ currentName: string;
24
+ /** Desired semantic base name (e.g., "timeoutId", "error") */
25
+ baseName: string;
26
+ };
27
+ /**
28
+ * Collects rename operations and applies them with automatic stability logic.
29
+ *
30
+ * Usage:
31
+ * ```typescript
32
+ * const group = new RenameGroup();
33
+ *
34
+ * // Collect renames during traversal
35
+ * group.add({ scope: path.scope, currentName: "a", baseName: "timeoutId" });
36
+ * group.add({ scope: path.scope, currentName: "b", baseName: "timeoutId" });
37
+ *
38
+ * // Apply with automatic stability
39
+ * const count = group.apply();
40
+ * // Result: a → timeoutId, b → timeoutId2 (readable, 2+ unstable)
41
+ * // Or if only one: a → $timeoutId (stable, exactly 1 unstable)
42
+ * ```
43
+ *
44
+ * Stability rules (per scope, per baseName):
45
+ * - If exactly 1 unstable variable wants this name → stable (`$baseName`)
46
+ * - If 2+ unstable variables want the same name → readable only (`baseName`, `baseName2`, ...)
47
+ */
48
+ export declare class RenameGroup {
49
+ private entries;
50
+ /**
51
+ * Add a variable to be renamed.
52
+ * Already-stable names (starting with $) are automatically skipped.
53
+ */
54
+ add(entry: RenameEntry): void;
55
+ /**
56
+ * Apply all collected renames with automatic stability logic.
57
+ * Returns the number of transformations applied.
58
+ */
59
+ apply(): number;
60
+ }
61
+ export {};
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Helpers for the stable name prefix convention (§5.6-5.9 in workflow.md).
3
+ *
4
+ * The `$` prefix signals that a variable has been renamed stably by a transform
5
+ * and should be skipped by other transforms.
6
+ *
7
+ * - Stable names (`$timeoutId`): Readable AND deterministic across versions
8
+ * - Readable names (`timeoutId`): Semantic but order-dependent
9
+ */
10
+ const STABLE_PREFIX = "$";
11
+ /**
12
+ * Returns true if the name has been stable-renamed (starts with $).
13
+ * Transforms should skip variables with this prefix.
14
+ */
15
+ export const isStableRenamed = (name) => {
16
+ return name.startsWith(STABLE_PREFIX);
17
+ };
18
+ /**
19
+ * Creates a stable name by adding the $ prefix.
20
+ * Internal helper - prefer using RenameGroup which handles stability logic automatically.
21
+ */
22
+ const makeStableName = (baseName) => {
23
+ return `${STABLE_PREFIX}${baseName}`;
24
+ };
25
+ /**
26
+ * Finds the next available name slot in a scope.
27
+ */
28
+ const findAvailableName = (scope, baseName, startIndex, canBeStable) => {
29
+ let index = startIndex;
30
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
31
+ while (true) {
32
+ const suffix = index === 1 ? "" : String(index);
33
+ const candidateBase = `${baseName}${suffix}`;
34
+ const candidateStable = makeStableName(candidateBase);
35
+ const candidateReadable = candidateBase;
36
+ // Check if either form is already taken in this scope
37
+ const stableTaken = scope.hasBinding(candidateStable);
38
+ const readableTaken = scope.hasBinding(candidateReadable);
39
+ if (!stableTaken && !readableTaken) {
40
+ // This slot is available
41
+ const name = canBeStable ? candidateStable : candidateReadable;
42
+ return { name, nextIndex: index + 1 };
43
+ }
44
+ index++;
45
+ }
46
+ };
47
+ /**
48
+ * Collects rename operations and applies them with automatic stability logic.
49
+ *
50
+ * Usage:
51
+ * ```typescript
52
+ * const group = new RenameGroup();
53
+ *
54
+ * // Collect renames during traversal
55
+ * group.add({ scope: path.scope, currentName: "a", baseName: "timeoutId" });
56
+ * group.add({ scope: path.scope, currentName: "b", baseName: "timeoutId" });
57
+ *
58
+ * // Apply with automatic stability
59
+ * const count = group.apply();
60
+ * // Result: a → timeoutId, b → timeoutId2 (readable, 2+ unstable)
61
+ * // Or if only one: a → $timeoutId (stable, exactly 1 unstable)
62
+ * ```
63
+ *
64
+ * Stability rules (per scope, per baseName):
65
+ * - If exactly 1 unstable variable wants this name → stable (`$baseName`)
66
+ * - If 2+ unstable variables want the same name → readable only (`baseName`, `baseName2`, ...)
67
+ */
68
+ export class RenameGroup {
69
+ entries = [];
70
+ /**
71
+ * Add a variable to be renamed.
72
+ * Already-stable names (starting with $) are automatically skipped.
73
+ */
74
+ add(entry) {
75
+ if (isStableRenamed(entry.currentName))
76
+ return;
77
+ this.entries.push(entry);
78
+ }
79
+ /**
80
+ * Apply all collected renames with automatic stability logic.
81
+ * Returns the number of transformations applied.
82
+ */
83
+ apply() {
84
+ // Group entries by (scope, baseName) using a two-level Map
85
+ const scopeGroups = new Map();
86
+ for (const entry of this.entries) {
87
+ let baseNameMap = scopeGroups.get(entry.scope);
88
+ if (!baseNameMap) {
89
+ baseNameMap = new Map();
90
+ scopeGroups.set(entry.scope, baseNameMap);
91
+ }
92
+ let group = baseNameMap.get(entry.baseName);
93
+ if (!group) {
94
+ group = [];
95
+ baseNameMap.set(entry.baseName, group);
96
+ }
97
+ group.push(entry);
98
+ }
99
+ let transformationsApplied = 0;
100
+ // Process each scope
101
+ for (const [scope, baseNameMap] of scopeGroups) {
102
+ // Process each baseName group within this scope
103
+ for (const [baseName, group] of baseNameMap) {
104
+ // Determine if names can be stable (only 1 unstable entry for this baseName)
105
+ const canBeStable = group.length === 1;
106
+ // Track which index to try next
107
+ let index = 1;
108
+ for (const entry of group) {
109
+ // Find next available name slot
110
+ const targetName = findAvailableName(scope, baseName, index, canBeStable);
111
+ index = targetName.nextIndex;
112
+ if (entry.currentName !== targetName.name) {
113
+ scope.rename(entry.currentName, targetName.name);
114
+ transformationsApplied++;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return transformationsApplied;
120
+ }
121
+ }
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { allTransformIds } from "../../transforms/transform-registry.js";
3
3
  import { transformRegistry } from "../../transforms/transform-registry.js";
4
+ import { transformPresets } from "../../transforms/transform-presets.js";
4
5
  import { parseTransformManifest } from "./parse-transform-manifest.js";
5
6
  import { checkAllSnapshots } from "./check-snapshots.js";
6
7
  export const checkExpectedEvaluations = async (options) => {
@@ -24,6 +25,7 @@ export const checkExpectedEvaluations = async (options) => {
24
25
  testCasesDirectory,
25
26
  transformIds,
26
27
  transformRegistry,
28
+ recommendedTransformIds: transformPresets.recommended,
27
29
  });
28
30
  const allPresent = missingManifestEntries.length === 0 && snapshotIssues.length === 0;
29
31
  return {
@@ -0,0 +1,11 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ import type { SnapshotIssue } from "./check-snapshots.js";
3
+ type CheckRecommendedSnapshotOptions = {
4
+ basePath: string;
5
+ baseContent: string;
6
+ snapshotPath: string;
7
+ testCaseName: string;
8
+ transforms: Transform[];
9
+ };
10
+ export declare const checkRecommendedSnapshot: (options: CheckRecommendedSnapshotOptions) => Promise<SnapshotIssue | undefined>;
11
+ export {};
@@ -0,0 +1,53 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as prettier from "prettier";
3
+ import { transformContentWithMultiple } from "./transform-content.js";
4
+ const formatCode = async (code) => {
5
+ return prettier.format(code, { parser: "babel" });
6
+ };
7
+ const fileExists = async (filePath) => {
8
+ try {
9
+ await fs.access(filePath);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ };
16
+ export const checkRecommendedSnapshot = async (options) => {
17
+ const { basePath, baseContent, snapshotPath, testCaseName, transforms } = options;
18
+ const snapshotExists = await fileExists(snapshotPath);
19
+ // Run all recommended transforms to get the output
20
+ const result = await transformContentWithMultiple({
21
+ content: baseContent,
22
+ filePath: basePath,
23
+ transforms,
24
+ });
25
+ if (!result.ok) {
26
+ // Transform failed, skip this check
27
+ return undefined;
28
+ }
29
+ const formattedOutput = await formatCode(result.output);
30
+ if (snapshotExists) {
31
+ const snapshotContent = await fs.readFile(snapshotPath, "utf8");
32
+ const formattedSnapshot = await formatCode(snapshotContent);
33
+ // Check if snapshot is outdated (recommended snapshots are never "unnecessary")
34
+ if (formattedOutput !== formattedSnapshot) {
35
+ return {
36
+ transformId: "recommended",
37
+ testCase: testCaseName,
38
+ snapshotPath,
39
+ issue: "outdated",
40
+ };
41
+ }
42
+ }
43
+ else {
44
+ // Snapshot doesn't exist - it's always required for recommended
45
+ return {
46
+ transformId: "recommended",
47
+ testCase: testCaseName,
48
+ snapshotPath,
49
+ issue: "missing-required",
50
+ };
51
+ }
52
+ return undefined;
53
+ };
@@ -9,6 +9,7 @@ type CheckAllSnapshotsOptions = {
9
9
  testCasesDirectory: string;
10
10
  transformIds: string[];
11
11
  transformRegistry: Record<string, Transform>;
12
+ recommendedTransformIds: string[];
12
13
  };
13
14
  export declare const checkAllSnapshots: (options: CheckAllSnapshotsOptions) => Promise<SnapshotIssue[]>;
14
15
  export {};
@@ -1,7 +1,9 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import * as prettier from "prettier";
4
+ import { normalizeCode } from "../../core/normalize-code.js";
4
5
  import { transformContent } from "./transform-content.js";
6
+ import { checkRecommendedSnapshot } from "./check-recommended-snapshot.js";
5
7
  const formatCode = async (code) => {
6
8
  return prettier.format(code, { parser: "babel" });
7
9
  };
@@ -84,13 +86,18 @@ const getTestCaseNames = async (testCasesDirectory) => {
84
86
  return testCaseNames.toSorted();
85
87
  };
86
88
  export const checkAllSnapshots = async (options) => {
87
- const { testCasesDirectory, transformIds, transformRegistry } = options;
89
+ const { testCasesDirectory, transformIds, transformRegistry, recommendedTransformIds, } = options;
88
90
  const snapshotIssues = [];
89
91
  const testCaseNames = await getTestCaseNames(testCasesDirectory);
92
+ // Get the recommended transforms in order
93
+ const recommendedTransforms = recommendedTransformIds
94
+ .map((id) => transformRegistry[id])
95
+ .filter((t) => t !== undefined);
90
96
  for (const testCaseName of testCaseNames) {
91
97
  const basePath = path.join(testCasesDirectory, testCaseName, "base.js");
92
98
  const baseContent = await fs.readFile(basePath, "utf8");
93
- const formattedBase = await formatCode(baseContent);
99
+ const formattedBase = await normalizeCode(baseContent);
100
+ // Check individual transform snapshots
94
101
  for (const transformId of transformIds) {
95
102
  const snapshotPath = path.join(testCasesDirectory, testCaseName, `${transformId}.js`);
96
103
  const issue = await checkSnapshot({
@@ -106,6 +113,18 @@ export const checkAllSnapshots = async (options) => {
106
113
  snapshotIssues.push(issue);
107
114
  }
108
115
  }
116
+ // Check recommended snapshot (always required)
117
+ const recommendedSnapshotPath = path.join(testCasesDirectory, testCaseName, "recommended.js");
118
+ const recommendedIssue = await checkRecommendedSnapshot({
119
+ basePath,
120
+ baseContent,
121
+ snapshotPath: recommendedSnapshotPath,
122
+ testCaseName,
123
+ transforms: recommendedTransforms,
124
+ });
125
+ if (recommendedIssue) {
126
+ snapshotIssues.push(recommendedIssue);
127
+ }
109
128
  }
110
129
  return snapshotIssues;
111
130
  };
@@ -11,4 +11,9 @@ export declare const transformContent: (options: {
11
11
  filePath: string;
12
12
  transform: Transform;
13
13
  }) => Promise<TransformContentResult>;
14
+ export declare const transformContentWithMultiple: (options: {
15
+ content: string;
16
+ filePath: string;
17
+ transforms: Transform[];
18
+ }) => Promise<TransformContentResult>;
14
19
  export {};
@@ -2,24 +2,34 @@ import { buildProjectGraph } from "../../core/project-graph.js";
2
2
  import { generateCode } from "../../cli/generate-code.js";
3
3
  export const transformContent = async (options) => {
4
4
  const { content, filePath, transform } = options;
5
+ return transformContentWithMultiple({
6
+ content,
7
+ filePath,
8
+ transforms: [transform],
9
+ });
10
+ };
11
+ export const transformContentWithMultiple = async (options) => {
12
+ const { content, filePath, transforms } = options;
5
13
  const projectGraph = buildProjectGraph([{ path: filePath, content }]);
6
14
  const file = projectGraph.files.get(filePath);
7
15
  if (!file) {
8
16
  return { ok: false, error: "Failed to parse input" };
9
17
  }
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
- };
18
+ for (const transform of transforms) {
19
+ try {
20
+ await transform.transform({
21
+ projectGraph,
22
+ currentFile: undefined,
23
+ options: {},
24
+ });
25
+ }
26
+ catch (error) {
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ return {
29
+ ok: false,
30
+ error: `Transform '${transform.id}' failed: ${message}`,
31
+ };
32
+ }
23
33
  }
24
34
  const generated = generateCode(file.ast);
25
35
  const output = generated === ""
@@ -1,63 +1,40 @@
1
1
  import { createRequire } from "node:module";
2
+ import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
2
3
  import { getFilesToProcess, } from "../../core/types.js";
3
4
  const require = createRequire(import.meta.url);
4
5
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5
6
  const traverse = require("@babel/traverse").default;
6
7
  const BASE_NAME = "caughtError";
7
- const getRenamedAncestorCatchCount = (path, renamedCatches) => {
8
- let count = 0;
9
- for (const ancestor of path.getAncestry()) {
10
- if (ancestor === path)
11
- continue;
12
- if (!ancestor.isCatchClause())
13
- continue;
14
- if (renamedCatches.has(ancestor.node))
15
- count++;
16
- }
17
- return count;
18
- };
19
- const getTargetCaughtErrorName = (renamedAncestorCatchCount) => {
20
- if (renamedAncestorCatchCount === 0)
21
- return BASE_NAME;
22
- return `${BASE_NAME}${renamedAncestorCatchCount + 1}`;
23
- };
24
- const getCatchParameterNameIfEligible = (path) => {
25
- const parameter = path.node.param;
26
- if (!parameter)
27
- return;
28
- if (parameter.type !== "Identifier")
29
- return;
30
- if (parameter.name.length > 2)
31
- return;
32
- return parameter.name;
33
- };
34
8
  export const renameCatchParametersTransform = {
35
9
  id: "rename-catch-parameters",
36
- description: "Renames minified catch clause parameters to caughtError/caughtError2/...",
10
+ description: "Renames catch clause parameters to $caughtError/$caughtError2/...",
37
11
  scope: "file",
38
12
  parallelizable: true,
39
13
  transform(context) {
40
14
  let nodesVisited = 0;
41
15
  let transformationsApplied = 0;
42
16
  for (const fileInfo of getFilesToProcess(context)) {
43
- const renamedCatches = new WeakMap();
17
+ const group = new RenameGroup();
44
18
  traverse(fileInfo.ast, {
45
19
  CatchClause(path) {
46
20
  nodesVisited++;
47
- const currentName = getCatchParameterNameIfEligible(path);
48
- if (!currentName)
21
+ const parameter = path.node.param;
22
+ if (!parameter)
49
23
  return;
50
- const renamedAncestorCatchCount = getRenamedAncestorCatchCount(path, renamedCatches);
51
- const targetName = getTargetCaughtErrorName(renamedAncestorCatchCount);
52
- if (currentName === targetName)
24
+ if (parameter.type !== "Identifier")
53
25
  return;
54
- if (path.scope.hasBinding(targetName))
26
+ const currentName = parameter.name;
27
+ // Skip already-stable names
28
+ if (isStableRenamed(currentName))
55
29
  return;
56
- path.scope.rename(currentName, targetName);
57
- renamedCatches.set(path.node, targetName);
58
- transformationsApplied++;
30
+ group.add({
31
+ scope: path.scope,
32
+ currentName,
33
+ baseName: BASE_NAME,
34
+ });
59
35
  },
60
36
  });
37
+ transformationsApplied += group.apply();
61
38
  }
62
39
  return Promise.resolve({
63
40
  nodesVisited,
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { isIdentifierName, isKeyword, isStrictBindReservedWord, } from "@babel/helper-validator-identifier";
3
3
  import { getFilesToProcess, } from "../../core/types.js";
4
+ import { RenameGroup } from "../../core/stable-naming.js";
4
5
  import { isBindingContext } from "./binding-context.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -22,7 +23,7 @@ const isValidBindingIdentifier = (name) => {
22
23
  return false;
23
24
  return true;
24
25
  };
25
- const processObjectPattern = (path, stats) => {
26
+ const processObjectPattern = (path, group) => {
26
27
  // Only process in binding contexts, not assignment expressions
27
28
  if (!isBindingContext(path))
28
29
  return;
@@ -84,14 +85,12 @@ const processObjectPattern = (path, stats) => {
84
85
  referencePath.scope.hasBinding(keyName));
85
86
  if (wouldBeShadowed)
86
87
  continue;
87
- // Rename the variable in the scope
88
- bindingScope.rename(aliasName, keyName);
89
- // Update the destructuring to use shorthand
90
- // For { a: b } -> { a }
91
- // For { a: b = 1 } -> { a = 1 }
92
- property.shorthand = true;
93
- aliasIdentifier.name = keyName;
94
- stats.transformationsApplied++;
88
+ // Add to rename group for batch processing with stability logic
89
+ group.add({
90
+ scope: bindingScope,
91
+ currentName: aliasName,
92
+ baseName: keyName,
93
+ });
95
94
  }
96
95
  };
97
96
  export const renameDestructuredAliasesTransform = {
@@ -101,18 +100,20 @@ export const renameDestructuredAliasesTransform = {
101
100
  parallelizable: true,
102
101
  transform(context) {
103
102
  let nodesVisited = 0;
104
- const stats = { transformationsApplied: 0 };
103
+ let transformationsApplied = 0;
105
104
  for (const fileInfo of getFilesToProcess(context)) {
105
+ const group = new RenameGroup();
106
106
  traverse(fileInfo.ast, {
107
107
  ObjectPattern(path) {
108
108
  nodesVisited++;
109
- processObjectPattern(path, stats);
109
+ processObjectPattern(path, group);
110
110
  },
111
111
  });
112
+ transformationsApplied += group.apply();
112
113
  }
113
114
  return Promise.resolve({
114
115
  nodesVisited,
115
- transformationsApplied: stats.transformationsApplied,
116
+ transformationsApplied,
116
117
  });
117
118
  },
118
119
  };
@@ -1,5 +1,4 @@
1
1
  import type { NodePath } from "@babel/traverse";
2
2
  import type { ArrowFunctionExpression, FunctionDeclaration, FunctionExpression } from "@babel/types";
3
- export declare const processEventHandlerFunction: (path: NodePath<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>, stats: {
4
- transformationsApplied: number;
5
- }) => void;
3
+ import { type RenameGroup } from "../../core/stable-naming.js";
4
+ export declare const processEventHandlerFunction: (path: NodePath<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression>, group: RenameGroup) => void;
@@ -1,3 +1,4 @@
1
+ import { isStableRenamed } from "../../core/stable-naming.js";
1
2
  import { getEventParameterUsage, isHighConfidenceEventParameter, } from "./event-parameter-usage.js";
2
3
  import { getTargetEventName } from "./get-target-event-name.js";
3
4
  import { canRenameBindingSafely } from "./can-rename-binding-safely.js";
@@ -23,7 +24,7 @@ const wouldShadowReferencedImplicitGlobal = (functionPath, targetName) => {
23
24
  });
24
25
  return foundReference;
25
26
  };
26
- export const processEventHandlerFunction = (path, stats) => {
27
+ export const processEventHandlerFunction = (path, group) => {
27
28
  const parameters = path.node.params;
28
29
  if (parameters.length !== 1)
29
30
  return;
@@ -32,12 +33,12 @@ export const processEventHandlerFunction = (path, stats) => {
32
33
  return;
33
34
  if (parameter.type !== "Identifier")
34
35
  return;
35
- // 1-2 character parameter names are a strong minification signal (e.g. `e`, `t`, `WA`).
36
- // Keeping this conservative avoids renaming common intentional abbreviations (e.g. `evt`).
37
- if (parameter.name.length > 2)
36
+ const currentName = parameter.name;
37
+ // Skip already-stable names
38
+ if (isStableRenamed(currentName))
38
39
  return;
39
40
  const bindingScope = path.scope;
40
- const binding = bindingScope.getBinding(parameter.name);
41
+ const binding = bindingScope.getBinding(currentName);
41
42
  if (!binding)
42
43
  return;
43
44
  if (!binding.constant)
@@ -46,14 +47,17 @@ export const processEventHandlerFunction = (path, stats) => {
46
47
  if (!isHighConfidenceEventParameter(usage))
47
48
  return;
48
49
  const targetName = getTargetEventName(usage);
49
- if (parameter.name === targetName)
50
+ if (currentName === targetName)
50
51
  return;
51
52
  if (wouldShadowReferencedImplicitGlobal(path, targetName))
52
53
  return;
53
54
  if (wouldShadowReferencedOuterBinding(path, targetName))
54
55
  return;
55
- if (!canRenameBindingSafely(bindingScope, parameter.name, targetName))
56
+ if (!canRenameBindingSafely(bindingScope, currentName, targetName))
56
57
  return;
57
- bindingScope.rename(parameter.name, targetName);
58
- stats.transformationsApplied++;
58
+ group.add({
59
+ scope: bindingScope,
60
+ currentName,
61
+ baseName: targetName,
62
+ });
59
63
  };
@@ -1,2 +1,2 @@
1
- import type { Transform } from "../../core/types.js";
1
+ import { type Transform } from "../../core/types.js";
2
2
  export declare const renameEventParametersTransform: Transform;