miniread 1.0.0 → 1.1.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 +1 -1
- package/dist/scripts/evaluate/parse-transform-manifest.js +1 -1
- 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 +15 -34
- 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-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-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-registry.js +18 -4
- package/package.json +29 -23
- package/transform-manifest.json +96 -26
- 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
|
@@ -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,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,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,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,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,23 @@
|
|
|
1
|
+
import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
|
|
2
|
+
const BASE_NAME = "hasRunRef";
|
|
3
|
+
// Cap retries to avoid pathological loops while handling large bundles.
|
|
4
|
+
const MAX_CANDIDATES = 1000;
|
|
5
|
+
export const getTargetName = (bindingScope, binding) => {
|
|
6
|
+
const programScope = bindingScope.getProgramParent();
|
|
7
|
+
for (let index = 0; index < MAX_CANDIDATES; index++) {
|
|
8
|
+
const candidate = index === 0 ? BASE_NAME : `${BASE_NAME}${index + 1}`;
|
|
9
|
+
if (!isValidBindingIdentifier(candidate))
|
|
10
|
+
continue;
|
|
11
|
+
// Avoid shadowing bindings in parent scopes.
|
|
12
|
+
if (bindingScope.hasBinding(candidate))
|
|
13
|
+
continue;
|
|
14
|
+
if (Object.hasOwn(programScope.globals, candidate))
|
|
15
|
+
continue;
|
|
16
|
+
const wouldBeShadowed = binding.referencePaths.some((referencePath) => referencePath.scope !== bindingScope &&
|
|
17
|
+
referencePath.scope.hasBinding(candidate));
|
|
18
|
+
if (wouldBeShadowed)
|
|
19
|
+
continue;
|
|
20
|
+
return candidate;
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const isUseReferenceFalseInitializer = (declarator) => {
|
|
2
|
+
const init = declarator.init;
|
|
3
|
+
if (!init)
|
|
4
|
+
return false;
|
|
5
|
+
if (init.type !== "CallExpression")
|
|
6
|
+
return false;
|
|
7
|
+
if (init.arguments.length !== 1)
|
|
8
|
+
return false;
|
|
9
|
+
const argument = init.arguments[0];
|
|
10
|
+
if (argument?.type !== "BooleanLiteral")
|
|
11
|
+
return false;
|
|
12
|
+
if (argument.value)
|
|
13
|
+
return false;
|
|
14
|
+
const callee = init.callee;
|
|
15
|
+
if (callee.type === "Identifier") {
|
|
16
|
+
return callee.name === "useRef";
|
|
17
|
+
}
|
|
18
|
+
if (callee.type !== "MemberExpression")
|
|
19
|
+
return false;
|
|
20
|
+
if (callee.computed)
|
|
21
|
+
return false;
|
|
22
|
+
if (callee.property.type !== "Identifier")
|
|
23
|
+
return false;
|
|
24
|
+
return callee.property.name === "useRef";
|
|
25
|
+
};
|
|
@@ -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,55 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { getReferenceUsage } from "./get-reference-usage.js";
|
|
3
|
+
import { getTargetName } from "./get-target-name.js";
|
|
4
|
+
import { isUseReferenceFalseInitializer } from "./is-use-reference-false-initializer.js";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const renameUseReferenceGuardsTransform = {
|
|
9
|
+
id: "rename-use-reference-guards",
|
|
10
|
+
description: "Renames boolean useRef(false) guard variables to hasRunRef/hasRunRef2/...",
|
|
11
|
+
scope: "file",
|
|
12
|
+
parallelizable: true,
|
|
13
|
+
transform(context) {
|
|
14
|
+
const { projectGraph } = context;
|
|
15
|
+
let nodesVisited = 0;
|
|
16
|
+
let transformationsApplied = 0;
|
|
17
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
18
|
+
traverse(fileInfo.ast, {
|
|
19
|
+
VariableDeclarator(path) {
|
|
20
|
+
nodesVisited++;
|
|
21
|
+
const id = path.node.id;
|
|
22
|
+
if (id.type !== "Identifier")
|
|
23
|
+
return;
|
|
24
|
+
if (id.name.length > 2)
|
|
25
|
+
return;
|
|
26
|
+
if (!isUseReferenceFalseInitializer(path.node))
|
|
27
|
+
return;
|
|
28
|
+
const binding = path.scope.getBinding(id.name);
|
|
29
|
+
if (!binding)
|
|
30
|
+
return;
|
|
31
|
+
if (!binding.constant)
|
|
32
|
+
return;
|
|
33
|
+
const usage = getReferenceUsage(binding);
|
|
34
|
+
if (!usage.isSafe ||
|
|
35
|
+
!usage.hasRead ||
|
|
36
|
+
!usage.hasTrueWrite ||
|
|
37
|
+
usage.hasFalseWrite)
|
|
38
|
+
return;
|
|
39
|
+
const targetName = getTargetName(path.scope, binding);
|
|
40
|
+
if (!targetName)
|
|
41
|
+
return;
|
|
42
|
+
if (id.name === targetName)
|
|
43
|
+
return;
|
|
44
|
+
path.scope.rename(id.name, targetName);
|
|
45
|
+
transformationsApplied++;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return Promise.resolve({
|
|
50
|
+
nodesVisited,
|
|
51
|
+
transformationsApplied,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export { renameUseReferenceGuardsTransform };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { VariableDeclaration } from "@babel/types";
|
|
2
|
+
export declare const createVariableDeclarationFromDeclarator: (kind: VariableDeclaration["kind"], declarator: VariableDeclaration["declarations"][number], declare: VariableDeclaration["declare"]) => VariableDeclaration;
|