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.
Files changed (98) hide show
  1. package/README.md +2 -2
  2. package/dist/scripts/evaluate/check-expected-evaluations.d.ts +17 -0
  3. package/dist/scripts/evaluate/check-expected-evaluations.js +35 -0
  4. package/dist/scripts/evaluate/check-snapshots.d.ts +14 -0
  5. package/dist/scripts/evaluate/check-snapshots.js +111 -0
  6. package/dist/scripts/evaluate/clone-metrics-report.d.ts +2 -0
  7. package/dist/scripts/evaluate/clone-metrics-report.js +6 -0
  8. package/dist/scripts/evaluate/create-evaluate-command.js +5 -20
  9. package/dist/scripts/evaluate/evaluate-help-text.d.ts +1 -0
  10. package/dist/scripts/evaluate/evaluate-help-text.js +21 -0
  11. package/dist/scripts/evaluate/pair-evaluator.d.ts +1 -0
  12. package/dist/scripts/evaluate/pair-evaluator.js +14 -3
  13. package/dist/scripts/evaluate/parse-evaluate-cli-options.d.ts +2 -0
  14. package/dist/scripts/evaluate/parse-evaluate-cli-options.js +3 -1
  15. package/dist/scripts/evaluate/parse-transform-manifest.d.ts +2 -2
  16. package/dist/scripts/evaluate/parse-transform-manifest.js +2 -2
  17. package/dist/scripts/evaluate/relative-patch-path.d.ts +7 -0
  18. package/dist/scripts/evaluate/relative-patch-path.js +22 -0
  19. package/dist/scripts/evaluate/run-check-mode.d.ts +8 -0
  20. package/dist/scripts/evaluate/run-check-mode.js +116 -0
  21. package/dist/scripts/evaluate/run-evaluate-cli.js +11 -3
  22. package/dist/scripts/evaluate/run-evaluations.js +1 -0
  23. package/dist/scripts/evaluate/transform-content.d.ts +14 -0
  24. package/dist/scripts/evaluate/transform-content.js +31 -0
  25. package/dist/scripts/evaluate/transform-manifest.d.ts +0 -2
  26. package/dist/scripts/evaluate/transform-manifest.js +16 -35
  27. package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.d.ts +2 -0
  28. package/dist/transforms/expand-return-sequence/expand-return-sequence-transform.js +91 -0
  29. package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.d.ts +3 -0
  30. package/dist/transforms/expand-sequence-expressions/expand-expression-statement-sequence.js +57 -0
  31. package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.d.ts +2 -0
  32. package/dist/transforms/expand-sequence-expressions/expand-sequence-expressions-transform.js +34 -0
  33. package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.d.ts +3 -0
  34. package/dist/transforms/expand-sequence-expressions/expand-variable-declaration-sequence.js +93 -0
  35. package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.d.ts +3 -0
  36. package/dist/transforms/expand-sequence-expressions-v2/expand-expression-statement-sequence.js +55 -0
  37. package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.d.ts +3 -0
  38. package/dist/transforms/expand-sequence-expressions-v2/expand-return-sequence.js +86 -0
  39. package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.d.ts +2 -0
  40. package/dist/transforms/expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.js +41 -0
  41. package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.d.ts +3 -0
  42. package/dist/transforms/expand-sequence-expressions-v2/expand-variable-declaration-sequence.js +93 -0
  43. package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.d.ts +3 -0
  44. package/dist/transforms/expand-sequence-expressions-v3/expand-expression-statement-sequence.js +64 -0
  45. package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.d.ts +3 -0
  46. package/dist/transforms/expand-sequence-expressions-v3/expand-return-sequence.js +91 -0
  47. package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.d.ts +2 -0
  48. package/dist/transforms/expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.js +48 -0
  49. package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.d.ts +3 -0
  50. package/dist/transforms/expand-sequence-expressions-v3/expand-throw-sequence.js +101 -0
  51. package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.d.ts +3 -0
  52. package/dist/transforms/expand-sequence-expressions-v3/expand-variable-declaration-sequence.js +99 -0
  53. package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.d.ts +2 -0
  54. package/dist/transforms/expand-throw-sequence/expand-throw-sequence-transform.js +117 -0
  55. package/dist/transforms/rename-binding/get-target-name.d.ts +4 -0
  56. package/dist/transforms/rename-binding/get-target-name.js +25 -0
  57. package/dist/transforms/rename-binding/is-valid-binding-identifier.d.ts +1 -0
  58. package/dist/transforms/rename-binding/is-valid-binding-identifier.js +10 -0
  59. package/dist/transforms/rename-destructured-aliases/binding-context.d.ts +7 -0
  60. package/dist/transforms/rename-destructured-aliases/binding-context.js +83 -0
  61. package/dist/transforms/rename-destructured-aliases/rename-destructured-aliases-transform.d.ts +2 -0
  62. package/dist/transforms/rename-destructured-aliases/rename-destructured-aliases-transform.js +118 -0
  63. package/dist/transforms/rename-promise-executor-parameters/is-valid-binding-identifier.d.ts +1 -0
  64. package/dist/transforms/rename-promise-executor-parameters/is-valid-binding-identifier.js +10 -0
  65. package/dist/transforms/rename-promise-executor-parameters/promise-executor-heuristics.d.ts +6 -0
  66. package/dist/transforms/rename-promise-executor-parameters/promise-executor-heuristics.js +88 -0
  67. package/dist/transforms/rename-promise-executor-parameters/rename-binding-if-safe.d.ts +3 -0
  68. package/dist/transforms/rename-promise-executor-parameters/rename-binding-if-safe.js +40 -0
  69. package/dist/transforms/rename-promise-executor-parameters/rename-promise-executor-parameters-transform.d.ts +2 -0
  70. package/dist/transforms/rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js +76 -0
  71. package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.d.ts +2 -0
  72. package/dist/transforms/rename-timeout-ids/rename-timeout-ids-transform.js +86 -0
  73. package/dist/transforms/rename-use-reference-guards/get-member-expression-for-reference.d.ts +3 -0
  74. package/dist/transforms/rename-use-reference-guards/get-member-expression-for-reference.js +10 -0
  75. package/dist/transforms/rename-use-reference-guards/get-reference-usage.d.ts +9 -0
  76. package/dist/transforms/rename-use-reference-guards/get-reference-usage.js +47 -0
  77. package/dist/transforms/rename-use-reference-guards/get-target-name.d.ts +2 -0
  78. package/dist/transforms/rename-use-reference-guards/get-target-name.js +23 -0
  79. package/dist/transforms/rename-use-reference-guards/is-use-reference-false-initializer.d.ts +2 -0
  80. package/dist/transforms/rename-use-reference-guards/is-use-reference-false-initializer.js +25 -0
  81. package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.d.ts +1 -0
  82. package/dist/transforms/rename-use-reference-guards/is-valid-binding-identifier.js +10 -0
  83. package/dist/transforms/rename-use-reference-guards/rename-use-reference-guards-transform.d.ts +3 -0
  84. package/dist/transforms/rename-use-reference-guards/rename-use-reference-guards-transform.js +55 -0
  85. package/dist/transforms/split-variable-declarations/create-variable-declaration.d.ts +2 -0
  86. package/dist/transforms/split-variable-declarations/create-variable-declaration.js +28 -0
  87. package/dist/transforms/split-variable-declarations/is-safe-to-split-variable-declaration.d.ts +3 -0
  88. package/dist/transforms/split-variable-declarations/is-safe-to-split-variable-declaration.js +52 -0
  89. package/dist/transforms/split-variable-declarations/split-variable-declarations-transform.d.ts +2 -0
  90. package/dist/transforms/split-variable-declarations/split-variable-declarations-transform.js +81 -0
  91. package/dist/transforms/transform-presets.js +2 -2
  92. package/dist/transforms/transform-registry.js +20 -4
  93. package/package.json +29 -23
  94. package/transform-manifest.json +110 -30
  95. package/dist/transforms/add-prefix/add-prefix-transform.d.ts +0 -2
  96. package/dist/transforms/add-prefix/add-prefix-transform.js +0 -40
  97. package/dist/transforms/add-suffix/add-suffix-transform.d.ts +0 -2
  98. package/dist/transforms/add-suffix/add-suffix-transform.js +0 -40
@@ -0,0 +1 @@
1
+ export declare const isValidBindingIdentifier: (name: string) => boolean;
@@ -0,0 +1,10 @@
1
+ import { isIdentifierName, isKeyword, isStrictBindReservedWord, } from "@babel/helper-validator-identifier";
2
+ export const isValidBindingIdentifier = (name) => {
3
+ if (!isIdentifierName(name))
4
+ return false;
5
+ if (isKeyword(name))
6
+ return false;
7
+ if (isStrictBindReservedWord(name, true))
8
+ return false;
9
+ return true;
10
+ };
@@ -0,0 +1,7 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { ObjectPattern } from "@babel/types";
3
+ /**
4
+ * Check if the ObjectPattern is in a binding context (declaration, function param, catch clause)
5
+ * vs. an assignment context (left side of assignment expression).
6
+ */
7
+ export declare const isBindingContext: (path: NodePath<ObjectPattern>) => boolean;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Check if an ArrayPattern is in a binding context.
3
+ * Used for nested destructuring like [{ id: i }].
4
+ */
5
+ const isArrayPatternInBindingContext = (path) => {
6
+ const parent = path.parent;
7
+ // VariableDeclarator: const [{ a: b }] = arr
8
+ if (parent.type === "VariableDeclarator")
9
+ return true;
10
+ // Function parameter: function([{ a: b }]) {}
11
+ if (parent.type === "FunctionDeclaration" ||
12
+ parent.type === "FunctionExpression" ||
13
+ parent.type === "ArrowFunctionExpression" ||
14
+ parent.type === "ObjectMethod" ||
15
+ parent.type === "ClassMethod" ||
16
+ parent.type === "ClassPrivateMethod") {
17
+ return true;
18
+ }
19
+ // Catch clause: catch ([{ error: e }]) {}
20
+ if (parent.type === "CatchClause")
21
+ return true;
22
+ // AssignmentPattern (e.g., function([{ a: b }] = []) {})
23
+ if (parent.type === "AssignmentPattern")
24
+ return true;
25
+ // Note: for-in/for-of with declarations (for (const [x] of items)) are handled
26
+ // by VariableDeclarator. Direct patterns (for ([x] of items)) are assignment
27
+ // contexts, not binding contexts.
28
+ // Nested in another ArrayPattern: [[{ a: b }]]
29
+ if (parent.type === "ArrayPattern" && path.parentPath) {
30
+ return isArrayPatternInBindingContext(path.parentPath);
31
+ }
32
+ // Nested in ObjectPattern: { data: [{ a: b }] }
33
+ if (parent.type === "ObjectProperty" && path.parentPath?.parentPath) {
34
+ const grandparentPath = path.parentPath.parentPath;
35
+ if (grandparentPath.isObjectPattern()) {
36
+ return isBindingContext(grandparentPath);
37
+ }
38
+ }
39
+ return false;
40
+ };
41
+ /**
42
+ * Check if the ObjectPattern is in a binding context (declaration, function param, catch clause)
43
+ * vs. an assignment context (left side of assignment expression).
44
+ */
45
+ export const isBindingContext = (path) => {
46
+ const parent = path.parent;
47
+ // VariableDeclarator: const { a: b } = obj
48
+ if (parent.type === "VariableDeclarator")
49
+ return true;
50
+ // Function parameter: function({ a: b }) {}
51
+ if (parent.type === "FunctionDeclaration" ||
52
+ parent.type === "FunctionExpression" ||
53
+ parent.type === "ArrowFunctionExpression" ||
54
+ parent.type === "ObjectMethod" ||
55
+ parent.type === "ClassMethod" ||
56
+ parent.type === "ClassPrivateMethod") {
57
+ return true;
58
+ }
59
+ // Catch clause: catch ({ error: e }) {}
60
+ if (parent.type === "CatchClause")
61
+ return true;
62
+ // AssignmentPattern (e.g., function({ a: b } = {}) {})
63
+ if (parent.type === "AssignmentPattern")
64
+ return true;
65
+ // Note: for-in/for-of with declarations (for (const {a} of items)) are handled
66
+ // by VariableDeclarator. Direct patterns (for ({a} of items)) are assignment
67
+ // contexts, not binding contexts.
68
+ // Nested destructuring in objects: { outer: { inner: x } }
69
+ // The inner ObjectPattern's parent is ObjectProperty, check grandparent
70
+ if (parent.type === "ObjectProperty" && path.parentPath.parentPath) {
71
+ const grandparentPath = path.parentPath.parentPath;
72
+ if (grandparentPath.isObjectPattern()) {
73
+ return isBindingContext(grandparentPath);
74
+ }
75
+ }
76
+ // Nested destructuring in arrays: [{ id: i }]
77
+ // The ObjectPattern's parent is ArrayPattern, check if array is in binding context
78
+ if (parent.type === "ArrayPattern") {
79
+ return isArrayPatternInBindingContext(path.parentPath);
80
+ }
81
+ // Assignment expression: { a: b } = obj (NOT a binding context)
82
+ return false;
83
+ };
@@ -0,0 +1,2 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ export declare const renameDestructuredAliasesTransform: Transform;
@@ -0,0 +1,118 @@
1
+ import { createRequire } from "node:module";
2
+ import { isIdentifierName, isKeyword, isStrictBindReservedWord, } from "@babel/helper-validator-identifier";
3
+ import { isBindingContext } from "./binding-context.js";
4
+ const require = createRequire(import.meta.url);
5
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
+ const traverse = require("@babel/traverse").default;
7
+ /**
8
+ * Check if the name is a valid binding identifier in ES modules (strict mode).
9
+ * Uses Babel's built-in helpers for authoritative validation.
10
+ */
11
+ const isValidBindingIdentifier = (name) => {
12
+ // Must be a valid identifier name (proper chars, not starting with digit, etc.)
13
+ if (!isIdentifierName(name))
14
+ return false;
15
+ // Must not be a keyword (return, if, etc.)
16
+ if (isKeyword(name))
17
+ return false;
18
+ // Must not be reserved for binding in strict mode (eval, arguments, let, etc.)
19
+ // The second parameter `true` indicates we're in a module context
20
+ if (isStrictBindReservedWord(name, true))
21
+ return false;
22
+ return true;
23
+ };
24
+ const processObjectPattern = (path, stats) => {
25
+ // Only process in binding contexts, not assignment expressions
26
+ if (!isBindingContext(path))
27
+ return;
28
+ for (const property of path.node.properties) {
29
+ // Skip rest elements
30
+ if (property.type === "RestElement")
31
+ continue;
32
+ // Skip computed properties (e.g., { [expr]: x })
33
+ if (property.computed)
34
+ continue;
35
+ // Skip shorthand properties (already in the form { foo })
36
+ if (property.shorthand)
37
+ continue;
38
+ // Key must be an identifier (the property name)
39
+ if (property.key.type !== "Identifier")
40
+ continue;
41
+ // Value can be an identifier or an AssignmentPattern (with default value)
42
+ // e.g., { a: b } or { a: b = 1 }
43
+ // Nested patterns like { a: { b: c } } are handled by visiting nested ObjectPatterns
44
+ let aliasIdentifier;
45
+ if (property.value.type === "Identifier") {
46
+ aliasIdentifier = property.value;
47
+ }
48
+ else if (property.value.type === "AssignmentPattern" &&
49
+ property.value.left.type === "Identifier") {
50
+ // { a: b = 1 } - the alias is in the left side of the assignment pattern
51
+ aliasIdentifier = property.value.left;
52
+ }
53
+ else {
54
+ continue;
55
+ }
56
+ const keyName = property.key.name;
57
+ const aliasName = aliasIdentifier.name;
58
+ // Skip if the alias is already the same as the property name
59
+ if (aliasName === keyName)
60
+ continue;
61
+ // Skip if property name is not a valid binding identifier
62
+ if (!isValidBindingIdentifier(keyName))
63
+ continue;
64
+ // Use parent's scope for binding checks and rename operations.
65
+ // path.scope for ObjectPattern returns a narrow scope that doesn't cover
66
+ // the function/catch body. path.parentPath.scope gives the proper scope.
67
+ const bindingScope = path.parentPath.scope;
68
+ // Skip if the target name already exists in scope or is a referenced global
69
+ // (e.g., don't rename to 'console' if the code uses console.log())
70
+ if (bindingScope.hasBinding(keyName))
71
+ continue;
72
+ const programScope = bindingScope.getProgramParent();
73
+ if (Object.hasOwn(programScope.globals, keyName))
74
+ continue;
75
+ // Get the binding for the alias - skip if it doesn't exist (safety check)
76
+ const binding = bindingScope.getBinding(aliasName);
77
+ if (!binding)
78
+ continue;
79
+ // Skip if any reference to aliasName is in a child scope that shadows keyName.
80
+ // Example: const { p: x } = obj; { const p = 1; console.log(x); }
81
+ // If we rename x -> p, the console.log(x) would incorrectly reference inner p.
82
+ const wouldBeShadowed = binding.referencePaths.some((referencePath) => referencePath.scope !== bindingScope &&
83
+ referencePath.scope.hasBinding(keyName));
84
+ if (wouldBeShadowed)
85
+ continue;
86
+ // Rename the variable in the scope
87
+ bindingScope.rename(aliasName, keyName);
88
+ // Update the destructuring to use shorthand
89
+ // For { a: b } -> { a }
90
+ // For { a: b = 1 } -> { a = 1 }
91
+ property.shorthand = true;
92
+ aliasIdentifier.name = keyName;
93
+ stats.transformationsApplied++;
94
+ }
95
+ };
96
+ export const renameDestructuredAliasesTransform = {
97
+ id: "rename-destructured-aliases",
98
+ description: "Renames destructuring aliases to match property names (e.g., { foo: x } → { foo })",
99
+ scope: "file",
100
+ parallelizable: true,
101
+ transform(context) {
102
+ const { projectGraph } = context;
103
+ let nodesVisited = 0;
104
+ const stats = { transformationsApplied: 0 };
105
+ for (const [, fileInfo] of projectGraph.files) {
106
+ traverse(fileInfo.ast, {
107
+ ObjectPattern(path) {
108
+ nodesVisited++;
109
+ processObjectPattern(path, stats);
110
+ },
111
+ });
112
+ }
113
+ return Promise.resolve({
114
+ nodesVisited,
115
+ transformationsApplied: stats.transformationsApplied,
116
+ });
117
+ },
118
+ };
@@ -0,0 +1 @@
1
+ export declare const isValidBindingIdentifier: (name: string) => boolean;
@@ -0,0 +1,10 @@
1
+ import { isIdentifierName, isKeyword, isStrictBindReservedWord, } from "@babel/helper-validator-identifier";
2
+ export const isValidBindingIdentifier = (name) => {
3
+ if (!isIdentifierName(name))
4
+ return false;
5
+ if (isKeyword(name))
6
+ return false;
7
+ if (isStrictBindReservedWord(name, true))
8
+ return false;
9
+ return true;
10
+ };
@@ -0,0 +1,6 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { ArrowFunctionExpression, FunctionExpression, NewExpression } from "@babel/types";
3
+ export declare const getExecutorFunctionIfEligible: (path: NodePath<NewExpression>) => NodePath<FunctionExpression | ArrowFunctionExpression> | undefined;
4
+ export declare const isDirectCallOfBinding: (referencePath: NodePath) => boolean;
5
+ export declare const isAssignedToOnHandlerProperty: (referencePath: NodePath) => boolean;
6
+ export declare const wouldShadowReferencedOuterBinding: (executorPath: NodePath<FunctionExpression | ArrowFunctionExpression>, targetName: string) => boolean;
@@ -0,0 +1,88 @@
1
+ import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
2
+ const allowedOnHandlerPropertyNames = new Set([
3
+ "onabort",
4
+ "onerror",
5
+ "ontimeout",
6
+ ]);
7
+ const isExecutorParameterEligible = (parameter) => {
8
+ // 1-2 character parameter names are a strong minification signal (e.g. `r`, `e`, `WA`, `BA`).
9
+ // Keeping this conservative prevents renaming intentionally-named parameters in non-minified code.
10
+ if (parameter.name.length > 2)
11
+ return false;
12
+ if (!isValidBindingIdentifier(parameter.name))
13
+ return false;
14
+ return true;
15
+ };
16
+ export const getExecutorFunctionIfEligible = (path) => {
17
+ // Intentionally does NOT require the constructor callee to be `Promise`.
18
+ // Bundled/minified code frequently uses Promise polyfills like `new K((a,b)=>...)`,
19
+ // so we treat the executor signature + usage heuristics as the "promise-like" signal.
20
+ if (path.node.arguments.length !== 1)
21
+ return;
22
+ const executor = path.get("arguments.0");
23
+ if (!executor.isFunctionExpression() &&
24
+ !executor.isArrowFunctionExpression()) {
25
+ return;
26
+ }
27
+ const parameters = executor.node.params;
28
+ if (parameters.length !== 2)
29
+ return;
30
+ const [firstParameter, secondParameter] = parameters;
31
+ if (!firstParameter || !secondParameter)
32
+ return;
33
+ if (firstParameter.type !== "Identifier")
34
+ return;
35
+ if (secondParameter.type !== "Identifier")
36
+ return;
37
+ if (!isExecutorParameterEligible(firstParameter))
38
+ return;
39
+ if (!isExecutorParameterEligible(secondParameter))
40
+ return;
41
+ return executor;
42
+ };
43
+ export const isDirectCallOfBinding = (referencePath) => {
44
+ const parentPath = referencePath.parentPath;
45
+ if (!parentPath?.isCallExpression())
46
+ return false;
47
+ return referencePath.key === "callee";
48
+ };
49
+ export const isAssignedToOnHandlerProperty = (referencePath) => {
50
+ const parentPath = referencePath.parentPath;
51
+ if (!parentPath?.isAssignmentExpression())
52
+ return false;
53
+ if (parentPath.node.operator !== "=")
54
+ return false;
55
+ if (referencePath.key !== "right")
56
+ return false;
57
+ const left = parentPath.node.left;
58
+ if (left.type !== "MemberExpression")
59
+ return false;
60
+ if (left.computed)
61
+ return false;
62
+ if (left.property.type !== "Identifier")
63
+ return false;
64
+ if (!allowedOnHandlerPropertyNames.has(left.property.name))
65
+ return false;
66
+ return true;
67
+ };
68
+ export const wouldShadowReferencedOuterBinding = (executorPath, targetName) => {
69
+ const parentBinding = executorPath.scope.getBinding(targetName);
70
+ if (!parentBinding)
71
+ return false;
72
+ let foundReference = false;
73
+ executorPath.traverse({
74
+ Identifier(identifierPath) {
75
+ if (foundReference)
76
+ return;
77
+ if (identifierPath.node.name !== targetName)
78
+ return;
79
+ if (!identifierPath.isReferencedIdentifier())
80
+ return;
81
+ if (identifierPath.scope.getBinding(targetName) !== parentBinding)
82
+ return;
83
+ foundReference = true;
84
+ identifierPath.stop();
85
+ },
86
+ });
87
+ return foundReference;
88
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ export declare const canRenameBindingSafely: (bindingScope: NodePath["scope"], fromName: string, toName: string) => boolean;
3
+ export declare const renameBindingIfSafe: (bindingScope: NodePath["scope"], fromName: string, toName: string) => boolean;
@@ -0,0 +1,40 @@
1
+ import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
2
+ const hasShadowingRisk = (binding, bindingScope, targetName) => binding.referencePaths.some((referencePath) => {
3
+ if (referencePath.scope === bindingScope)
4
+ return false;
5
+ // Only consider bindings between this reference’s scope and the executor’s binding scope.
6
+ // This avoids incorrectly blocking safe renames due to unrelated outer bindings (e.g., an
7
+ // outer `resolve` that is never referenced inside the executor).
8
+ const hasBindingOptions = {
9
+ noGlobals: true,
10
+ noUids: true,
11
+ // Present in Babel scope implementation, but missing from our TypeScript types.
12
+ upToScope: bindingScope,
13
+ };
14
+ return referencePath.scope.hasBinding(targetName, hasBindingOptions);
15
+ });
16
+ export const canRenameBindingSafely = (bindingScope, fromName, toName) => {
17
+ if (fromName === toName)
18
+ return false;
19
+ if (!isValidBindingIdentifier(toName))
20
+ return false;
21
+ if (bindingScope.hasOwnBinding(toName))
22
+ return false;
23
+ const programScope = bindingScope.getProgramParent();
24
+ // `globals` contains only referenced (unbound) globals for this file, so this does not block
25
+ // in files where the identifier is never used.
26
+ if (Object.hasOwn(programScope.globals, toName))
27
+ return false;
28
+ const binding = bindingScope.getBinding(fromName);
29
+ if (!binding)
30
+ return false;
31
+ if (hasShadowingRisk(binding, bindingScope, toName))
32
+ return false;
33
+ return true;
34
+ };
35
+ export const renameBindingIfSafe = (bindingScope, fromName, toName) => {
36
+ if (!canRenameBindingSafely(bindingScope, fromName, toName))
37
+ return false;
38
+ bindingScope.rename(fromName, toName);
39
+ return true;
40
+ };
@@ -0,0 +1,2 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ export declare const renamePromiseExecutorParametersTransform: Transform;
@@ -0,0 +1,76 @@
1
+ import { createRequire } from "node:module";
2
+ import { getExecutorFunctionIfEligible, isAssignedToOnHandlerProperty, isDirectCallOfBinding, wouldShadowReferencedOuterBinding, } from "./promise-executor-heuristics.js";
3
+ import { canRenameBindingSafely, renameBindingIfSafe, } from "./rename-binding-if-safe.js";
4
+ const require = createRequire(import.meta.url);
5
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
+ const traverse = require("@babel/traverse").default;
7
+ export const renamePromiseExecutorParametersTransform = {
8
+ id: "rename-promise-executor-parameters",
9
+ description: "Renames promise-like executor parameters in `new X(executor)` to resolve/reject when usage is high-confidence",
10
+ scope: "file",
11
+ parallelizable: true,
12
+ transform(context) {
13
+ const { projectGraph } = context;
14
+ let nodesVisited = 0;
15
+ let transformationsApplied = 0;
16
+ for (const [, fileInfo] of projectGraph.files) {
17
+ traverse(fileInfo.ast, {
18
+ NewExpression(path) {
19
+ nodesVisited++;
20
+ const executorPath = getExecutorFunctionIfEligible(path);
21
+ if (!executorPath)
22
+ return;
23
+ const [resolveParameter, rejectParameter] = executorPath.node
24
+ .params;
25
+ const bindingScope = executorPath.scope;
26
+ const resolveBinding = bindingScope.getBinding(resolveParameter.name);
27
+ const rejectBinding = bindingScope.getBinding(rejectParameter.name);
28
+ if (!resolveBinding || !rejectBinding)
29
+ return;
30
+ const resolveIsCalled = resolveBinding.referencePaths.some((referencePath) => isDirectCallOfBinding(referencePath));
31
+ if (!resolveIsCalled)
32
+ return;
33
+ const resolveIsOnlyCalled = resolveBinding.referencePaths.every((referencePath) => isDirectCallOfBinding(referencePath));
34
+ if (!resolveIsOnlyCalled)
35
+ return;
36
+ const rejectIsCalled = rejectBinding.referencePaths.some((referencePath) => isDirectCallOfBinding(referencePath));
37
+ const rejectIsOnHandler = rejectBinding.referencePaths.some((referencePath) => isAssignedToOnHandlerProperty(referencePath));
38
+ if (!rejectIsCalled && !rejectIsOnHandler)
39
+ return;
40
+ const rejectIsOnlyCalledOrHandlerAssigned = rejectBinding.referencePaths.every((referencePath) => isDirectCallOfBinding(referencePath) ||
41
+ isAssignedToOnHandlerProperty(referencePath));
42
+ if (!rejectIsOnlyCalledOrHandlerAssigned)
43
+ return;
44
+ const resolveWouldShadow = wouldShadowReferencedOuterBinding(executorPath, "resolve");
45
+ const rejectWouldShadow = wouldShadowReferencedOuterBinding(executorPath, "reject");
46
+ const targetResolveName = "resolve";
47
+ const targetRejectName = "reject";
48
+ const wantsResolveRename = resolveParameter.name !== targetResolveName;
49
+ const wantsRejectRename = rejectParameter.name !== targetRejectName;
50
+ const canRenameResolve = !wantsResolveRename ||
51
+ (!resolveWouldShadow &&
52
+ canRenameBindingSafely(bindingScope, resolveParameter.name, targetResolveName));
53
+ const canRenameReject = !wantsRejectRename ||
54
+ (!rejectWouldShadow &&
55
+ canRenameBindingSafely(bindingScope, rejectParameter.name, targetRejectName));
56
+ // Avoid ending up with inconsistent executor signatures like `(WA, reject)` —
57
+ // only rename when both parameters are already correct or can be renamed safely.
58
+ if (!canRenameResolve || !canRenameReject)
59
+ return;
60
+ const renamedResolve = wantsResolveRename
61
+ ? renameBindingIfSafe(bindingScope, resolveParameter.name, targetResolveName)
62
+ : false;
63
+ const renamedReject = wantsRejectRename
64
+ ? renameBindingIfSafe(bindingScope, rejectParameter.name, targetRejectName)
65
+ : false;
66
+ if (!renamedResolve && !renamedReject)
67
+ return;
68
+ // Metrics represent renamed bindings, not "executors processed".
69
+ transformationsApplied +=
70
+ Number(renamedResolve) + Number(renamedReject);
71
+ },
72
+ });
73
+ }
74
+ return Promise.resolve({ nodesVisited, transformationsApplied });
75
+ },
76
+ };
@@ -0,0 +1,2 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ export declare const renameTimeoutIdsTransform: Transform;
@@ -0,0 +1,86 @@
1
+ import { createRequire } from "node:module";
2
+ import { getTargetName } from "../rename-binding/get-target-name.js";
3
+ const require = createRequire(import.meta.url);
4
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5
+ const traverse = require("@babel/traverse").default;
6
+ const BASE_NAME = "timeoutId";
7
+ const isSetTimeoutCall = (path, init) => {
8
+ if (init.callee.type !== "Identifier")
9
+ return false;
10
+ if (init.callee.name !== "setTimeout")
11
+ return false;
12
+ // If setTimeout is locally bound (imported or declared), semantics may differ.
13
+ if (path.scope.hasBinding("setTimeout", true))
14
+ return false;
15
+ return true;
16
+ };
17
+ const isClearTimeoutCallArgument = (referencePath, bindingName) => {
18
+ if (!referencePath.isIdentifier())
19
+ return false;
20
+ const callPath = referencePath.parentPath;
21
+ if (!callPath.isCallExpression())
22
+ return false;
23
+ const call = callPath.node;
24
+ if (call.callee.type !== "Identifier")
25
+ return false;
26
+ if (call.callee.name !== "clearTimeout")
27
+ return false;
28
+ // If clearTimeout is locally bound (imported or declared), semantics may differ.
29
+ if (callPath.scope.hasBinding("clearTimeout", true))
30
+ return false;
31
+ const argument0 = call.arguments[0];
32
+ if (argument0?.type !== "Identifier")
33
+ return false;
34
+ if (argument0 !== referencePath.node)
35
+ return false;
36
+ if (argument0.name !== bindingName)
37
+ return false;
38
+ return true;
39
+ };
40
+ export const renameTimeoutIdsTransform = {
41
+ id: "rename-timeout-ids",
42
+ description: "Renames setTimeout handle variables to timeoutId/timeoutId2/... when usage is only clearTimeout(...)",
43
+ scope: "file",
44
+ parallelizable: true,
45
+ transform(context) {
46
+ const { projectGraph } = context;
47
+ let nodesVisited = 0;
48
+ let transformationsApplied = 0;
49
+ for (const [, fileInfo] of projectGraph.files) {
50
+ traverse(fileInfo.ast, {
51
+ VariableDeclarator(path) {
52
+ nodesVisited++;
53
+ const id = path.node.id;
54
+ if (id.type !== "Identifier")
55
+ return;
56
+ if (id.name.length > 2)
57
+ return;
58
+ const init = path.node.init;
59
+ if (init?.type !== "CallExpression")
60
+ return;
61
+ if (!isSetTimeoutCall(path, init))
62
+ return;
63
+ const binding = path.scope.getBinding(id.name);
64
+ if (!binding)
65
+ return;
66
+ if (!binding.constant)
67
+ return;
68
+ if (binding.referencePaths.length === 0)
69
+ return;
70
+ if (!binding.referencePaths.every((referencePath) => isClearTimeoutCallArgument(referencePath, id.name)))
71
+ return;
72
+ const targetName = getTargetName(path.scope, binding, {
73
+ baseName: BASE_NAME,
74
+ });
75
+ if (!targetName)
76
+ return;
77
+ if (id.name === targetName)
78
+ return;
79
+ path.scope.rename(id.name, targetName);
80
+ transformationsApplied++;
81
+ },
82
+ });
83
+ }
84
+ return Promise.resolve({ nodesVisited, transformationsApplied });
85
+ },
86
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Identifier, MemberExpression, OptionalMemberExpression } from "@babel/types";
3
+ export declare const getMemberExpressionForReference: (referencePath: NodePath<Identifier>) => NodePath<MemberExpression | OptionalMemberExpression> | undefined;
@@ -0,0 +1,10 @@
1
+ export const getMemberExpressionForReference = (referencePath) => {
2
+ const parentPath = referencePath.parentPath;
3
+ if (parentPath.isMemberExpression() &&
4
+ parentPath.node.object === referencePath.node)
5
+ return parentPath;
6
+ if (parentPath.isOptionalMemberExpression() &&
7
+ parentPath.node.object === referencePath.node)
8
+ return parentPath;
9
+ return;
10
+ };
@@ -0,0 +1,9 @@
1
+ import type { Binding } from "@babel/traverse";
2
+ type ReferenceUsage = {
3
+ isSafe: boolean;
4
+ hasRead: boolean;
5
+ hasTrueWrite: boolean;
6
+ hasFalseWrite: boolean;
7
+ };
8
+ export declare const getReferenceUsage: (binding: Binding) => ReferenceUsage;
9
+ export {};
@@ -0,0 +1,47 @@
1
+ import { getMemberExpressionForReference } from "./get-member-expression-for-reference.js";
2
+ export const getReferenceUsage = (binding) => {
3
+ let hasRead = false;
4
+ let hasTrueWrite = false;
5
+ let hasFalseWrite = false;
6
+ for (const referencePath of binding.referencePaths) {
7
+ if (!referencePath.isIdentifier())
8
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
9
+ const memberPath = getMemberExpressionForReference(referencePath);
10
+ if (!memberPath)
11
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
12
+ const memberNode = memberPath.node;
13
+ if (memberNode.computed)
14
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
15
+ if (memberNode.property.type !== "Identifier" ||
16
+ memberNode.property.name !== "current") {
17
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
18
+ }
19
+ const parentPath = memberPath.parentPath;
20
+ if (parentPath.isAssignmentExpression() &&
21
+ parentPath.node.left === memberNode) {
22
+ if (parentPath.node.operator !== "=")
23
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
24
+ const right = parentPath.node.right;
25
+ if (right.type !== "BooleanLiteral")
26
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
27
+ if (right.value) {
28
+ hasTrueWrite = true;
29
+ }
30
+ else {
31
+ hasFalseWrite = true;
32
+ }
33
+ continue;
34
+ }
35
+ if (parentPath.isUpdateExpression() &&
36
+ parentPath.node.argument === memberNode) {
37
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
38
+ }
39
+ if (parentPath.isUnaryExpression() &&
40
+ parentPath.node.operator === "delete" &&
41
+ parentPath.node.argument === memberNode) {
42
+ return { isSafe: false, hasRead, hasTrueWrite, hasFalseWrite };
43
+ }
44
+ hasRead = true;
45
+ }
46
+ return { isSafe: true, hasRead, hasTrueWrite, hasFalseWrite };
47
+ };
@@ -0,0 +1,2 @@
1
+ import type { Binding, Scope } from "@babel/traverse";
2
+ export declare const getTargetName: (bindingScope: Scope, binding: Binding) => string | undefined;