miniread 1.52.0 → 1.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/is-type-only-global-reference.d.ts +2 -0
- package/dist/core/is-type-only-global-reference.js +33 -0
- package/dist/core/stable-naming.js +74 -5
- package/dist/transforms/_generated/manifest.js +5 -0
- package/dist/transforms/_generated/registry.js +2 -0
- package/dist/transforms/get-url-construction-kind.d.ts +5 -0
- package/dist/transforms/get-url-construction-kind.js +47 -0
- package/dist/transforms/is-allowed-url-constructor-in-scope.d.ts +2 -0
- package/dist/transforms/is-allowed-url-constructor-in-scope.js +70 -0
- package/dist/transforms/is-allowed-url-module-import-binding.d.ts +2 -0
- package/dist/transforms/is-allowed-url-module-import-binding.js +24 -0
- package/dist/transforms/is-allowed-url-namespace-binding-in-scope.d.ts +2 -0
- package/dist/transforms/is-allowed-url-namespace-binding-in-scope.js +35 -0
- package/dist/transforms/is-require-from-url-module.d.ts +15 -0
- package/dist/transforms/is-require-from-url-module.js +163 -0
- package/dist/transforms/rename-search-parameters-variables/get-member-expression-property-name.d.ts +2 -0
- package/dist/transforms/rename-search-parameters-variables/get-member-expression-property-name.js +20 -0
- package/dist/transforms/rename-search-parameters-variables/get-search-parameters-member-expression.d.ts +3 -0
- package/dist/transforms/rename-search-parameters-variables/get-search-parameters-member-expression.js +26 -0
- package/dist/transforms/rename-search-parameters-variables/manifest.json +20 -0
- package/dist/transforms/rename-search-parameters-variables/rename-search-parameters-variables-transform.d.ts +2 -0
- package/dist/transforms/rename-search-parameters-variables/rename-search-parameters-variables-transform.js +75 -0
- package/dist/transforms/rename-search-parameters-variables/search-parameters-usage-heuristics.d.ts +5 -0
- package/dist/transforms/rename-search-parameters-variables/search-parameters-usage-heuristics.js +151 -0
- package/dist/transforms/rename-url-variables/rename-url-variables-transform.js +13 -12
- package/package.json +1 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const isTypeOnlyGlobalReference = (path) => {
|
|
2
|
+
// `typeof Foo` in a type position still resolves `Foo` from the value
|
|
3
|
+
// namespace, so it can be affected by local shadowing.
|
|
4
|
+
if (path.findParent((parent) => parent.node.type === "TSTypeQuery"))
|
|
5
|
+
return false;
|
|
6
|
+
// TypeScript keeps value and type namespaces separate, so introducing a local
|
|
7
|
+
// value binding will not affect a purely type-position reference like
|
|
8
|
+
// `type T = Foo`. We only reserve globals that are referenced from the value
|
|
9
|
+
// namespace.
|
|
10
|
+
return Boolean(path.findParent((parent) => {
|
|
11
|
+
const type = parent.node.type;
|
|
12
|
+
if (!type.startsWith("TS"))
|
|
13
|
+
return false;
|
|
14
|
+
// Some TS-prefixed nodes are runtime constructs (e.g. `enum`, `namespace`,
|
|
15
|
+
// `export = Foo`) and contain real value-position expressions. Treat those
|
|
16
|
+
// contexts as non-type-only unless we find a more specific type node
|
|
17
|
+
// closer to the identifier.
|
|
18
|
+
if (type === "TSEnumDeclaration" ||
|
|
19
|
+
type === "TSEnumMember" ||
|
|
20
|
+
type === "TSExportAssignment" ||
|
|
21
|
+
type === "TSImportEqualsDeclaration" ||
|
|
22
|
+
type === "TSExternalModuleReference" ||
|
|
23
|
+
type === "TSModuleBlock" ||
|
|
24
|
+
type === "TSModuleDeclaration" ||
|
|
25
|
+
type === "TSNamespaceExportDeclaration")
|
|
26
|
+
return false;
|
|
27
|
+
return (type !== "TSAsExpression" &&
|
|
28
|
+
type !== "TSNonNullExpression" &&
|
|
29
|
+
type !== "TSSatisfiesExpression" &&
|
|
30
|
+
type !== "TSInstantiationExpression" &&
|
|
31
|
+
type !== "TSTypeAssertion");
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Stable names (`$timeoutId`): Readable AND deterministic across versions
|
|
8
8
|
* - Readable names (`timeoutId`): Semantic but order-dependent
|
|
9
9
|
*/
|
|
10
|
+
import { isTypeOnlyGlobalReference } from "./is-type-only-global-reference.js";
|
|
10
11
|
const STABLE_PREFIX = "$";
|
|
11
12
|
/**
|
|
12
13
|
* Returns true if the name has been stable-renamed (starts with $).
|
|
@@ -35,27 +36,95 @@ const hasShadowingRisk = (binding, bindingScope, targetName) => binding.referenc
|
|
|
35
36
|
};
|
|
36
37
|
return referencePath.scope.hasBinding(targetName, hasBindingOptions);
|
|
37
38
|
});
|
|
39
|
+
// Cache is keyed by the program `Scope` instance produced by Babel traversal,
|
|
40
|
+
// so it won't leak across separate traversals / transform passes.
|
|
41
|
+
const programValueGlobalsCache = new WeakMap();
|
|
42
|
+
const getProgramValueGlobals = (scope) => {
|
|
43
|
+
const programScope = scope.getProgramParent();
|
|
44
|
+
const cached = programValueGlobalsCache.get(programScope);
|
|
45
|
+
if (cached)
|
|
46
|
+
return cached;
|
|
47
|
+
const programGlobals = programScope.globals;
|
|
48
|
+
const globalNames = Object.keys(programGlobals);
|
|
49
|
+
if (globalNames.length === 0) {
|
|
50
|
+
const empty = new Set();
|
|
51
|
+
programValueGlobalsCache.set(programScope, empty);
|
|
52
|
+
return empty;
|
|
53
|
+
}
|
|
54
|
+
const globalNameSet = new Set(globalNames);
|
|
55
|
+
const valueGlobals = new Set();
|
|
56
|
+
programScope.path.traverse({
|
|
57
|
+
Identifier(path) {
|
|
58
|
+
const name = path.node.name;
|
|
59
|
+
if (!globalNameSet.has(name))
|
|
60
|
+
return;
|
|
61
|
+
if (!path.isReferencedIdentifier())
|
|
62
|
+
return;
|
|
63
|
+
if (isTypeOnlyGlobalReference(path))
|
|
64
|
+
return;
|
|
65
|
+
valueGlobals.add(name);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
// Cache is safe because we only ever use this set to prevent choosing rename
|
|
69
|
+
// targets that would shadow *existing* program-global references, and rename
|
|
70
|
+
// application should not introduce new free/global identifiers. If a future
|
|
71
|
+
// transform starts inserting new global references into the AST, this cache
|
|
72
|
+
// would need to be recomputed (or removed).
|
|
73
|
+
programValueGlobalsCache.set(programScope, valueGlobals);
|
|
74
|
+
return valueGlobals;
|
|
75
|
+
};
|
|
38
76
|
/**
|
|
39
77
|
* Finds the next available name slot in a scope.
|
|
40
78
|
*/
|
|
41
79
|
const findAvailableName = (scope, binding, baseName, startIndex, canBeStable, currentName) => {
|
|
42
80
|
let index = startIndex;
|
|
81
|
+
// Treat any identifier referenced as a program-global value as reserved.
|
|
82
|
+
//
|
|
83
|
+
// For readable renames we check both the readable and stable variants; this
|
|
84
|
+
// keeps the stable/readable "slot" aligned (e.g. an existing `$foo` blocks
|
|
85
|
+
// introducing `foo`) and prevents introducing a local binding that would
|
|
86
|
+
// shadow a global used anywhere in the file (even across sibling scopes).
|
|
87
|
+
//
|
|
88
|
+
// For stable-only renames we only need to ensure the stable identifier itself
|
|
89
|
+
// is free (stable names don't collide with readable ones).
|
|
90
|
+
// Safe to read once per call: `globals` are gathered during traversal; actual
|
|
91
|
+
// renames happen later in `RenameGroup.apply()`.
|
|
92
|
+
// Note: This is intentionally conservative and can reduce rename coverage in
|
|
93
|
+
// bundles with many free identifiers.
|
|
94
|
+
const programGlobals = getProgramValueGlobals(scope);
|
|
43
95
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
44
96
|
while (true) {
|
|
45
97
|
const suffix = index === 1 ? "" : String(index);
|
|
46
98
|
const candidateBase = `${baseName}${suffix}`;
|
|
47
99
|
const candidateStable = makeStableName(candidateBase);
|
|
48
100
|
const candidateReadable = candidateBase;
|
|
49
|
-
//
|
|
101
|
+
// Always reserve the stable name, even when we won't emit it, to keep the
|
|
102
|
+
// "slot" aligned across stable/readable naming and avoid collisions with
|
|
103
|
+
// already-stable identifiers.
|
|
50
104
|
const stableTaken = candidateStable === currentName
|
|
51
105
|
? false
|
|
52
|
-
: scope.hasBinding(candidateStable)
|
|
106
|
+
: scope.hasBinding(candidateStable) ||
|
|
107
|
+
programGlobals.has(candidateStable);
|
|
108
|
+
if (canBeStable) {
|
|
109
|
+
if (stableTaken) {
|
|
110
|
+
index++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Stable names (`$foo`) don't collide with readable names (`foo`), so when
|
|
114
|
+
// we know we'll emit a stable name, we only need to check the stable slot.
|
|
115
|
+
const name = candidateStable;
|
|
116
|
+
if (binding && hasShadowingRisk(binding, scope, name)) {
|
|
117
|
+
index++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
return { name, nextIndex: index + 1 };
|
|
121
|
+
}
|
|
53
122
|
const readableTaken = candidateReadable === currentName
|
|
54
123
|
? false
|
|
55
|
-
: scope.hasBinding(candidateReadable)
|
|
124
|
+
: scope.hasBinding(candidateReadable) ||
|
|
125
|
+
programGlobals.has(candidateReadable);
|
|
56
126
|
if (!stableTaken && !readableTaken) {
|
|
57
|
-
|
|
58
|
-
const name = canBeStable ? candidateStable : candidateReadable;
|
|
127
|
+
const name = candidateReadable;
|
|
59
128
|
if (binding && hasShadowingRisk(binding, scope, name)) {
|
|
60
129
|
index++;
|
|
61
130
|
continue;
|
|
@@ -201,6 +201,11 @@ const manifestData = {
|
|
|
201
201
|
notes: "Negative diff reduction in evaluation; keep optional.",
|
|
202
202
|
evaluations: { "legacy:evaluation": { "diffSizePercent": 100.00188483649043, "evaluatedAt": "2026-01-25T10:04:22.417Z", "changedLines": 0, "durationSeconds": 0, "stableNames": 0 } },
|
|
203
203
|
},
|
|
204
|
+
"rename-search-parameters-variables": {
|
|
205
|
+
recommended: true,
|
|
206
|
+
notes: "Measured with baseline none: 100.00% of original diff. Added to recommended for readability.",
|
|
207
|
+
evaluations: { "legacy:evaluation": { "diffSizePercent": 100, "evaluatedAt": "2026-01-25T09:49:29.000Z", "changedLines": 0, "durationSeconds": 0, "stableNames": 0 }, "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-01-26T07:05:01.592Z", "changedLines": 32, "durationSeconds": 54.402595083, "stableNames": 1358 } },
|
|
208
|
+
},
|
|
204
209
|
"rename-string-split-variables": {
|
|
205
210
|
recommended: true,
|
|
206
211
|
notes: "Measured with baseline none: 0.00%. Added to recommended for readability.",
|
|
@@ -39,6 +39,7 @@ import { renameRegexBuildersTransform } from "../rename-regex-builders/rename-re
|
|
|
39
39
|
import { renameReplaceChildParametersTransform } from "../rename-replace-child-parameters/rename-replace-child-parameters-transform.js";
|
|
40
40
|
import { renameRestParametersTransform } from "../rename-rest-parameters/rename-rest-parameters-transform.js";
|
|
41
41
|
import { renameSafePropertyAccessorParametersTransform } from "../rename-safe-property-accessor-parameters/rename-safe-property-accessor-parameters-transform.js";
|
|
42
|
+
import { renameSearchParametersVariablesTransform } from "../rename-search-parameters-variables/rename-search-parameters-variables-transform.js";
|
|
42
43
|
import { renameStringSplitVariablesTransform } from "../rename-string-split-variables/rename-string-split-variables-transform.js";
|
|
43
44
|
import { renameThisAliasesTransform } from "../rename-this-aliases/rename-this-aliases-transform.js";
|
|
44
45
|
import { renameTimeoutDurationParametersTransform } from "../rename-timeout-duration-parameters/rename-timeout-duration-parameters-transform.js";
|
|
@@ -96,6 +97,7 @@ export const transformRegistry = {
|
|
|
96
97
|
[renameReplaceChildParametersTransform.id]: renameReplaceChildParametersTransform,
|
|
97
98
|
[renameRestParametersTransform.id]: renameRestParametersTransform,
|
|
98
99
|
[renameSafePropertyAccessorParametersTransform.id]: renameSafePropertyAccessorParametersTransform,
|
|
100
|
+
[renameSearchParametersVariablesTransform.id]: renameSearchParametersVariablesTransform,
|
|
99
101
|
[renameStringSplitVariablesTransform.id]: renameStringSplitVariablesTransform,
|
|
100
102
|
[renameThisAliasesTransform.id]: renameThisAliasesTransform,
|
|
101
103
|
[renameTimeoutDurationParametersTransform.id]: renameTimeoutDurationParametersTransform,
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Scope } from "@babel/traverse";
|
|
2
|
+
import type { Expression } from "@babel/types";
|
|
3
|
+
type UrlConstructionKind = "identifier" | "member";
|
|
4
|
+
export declare const getUrlConstructionKind: (scope: Scope, node: Expression | null | undefined, constructorName: "URL" | "URLSearchParams") => UrlConstructionKind | undefined;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isAllowedUrlNamespaceBindingInScope } from "./is-allowed-url-namespace-binding-in-scope.js";
|
|
2
|
+
export const getUrlConstructionKind = (scope, node, constructorName) => {
|
|
3
|
+
if (node?.type !== "NewExpression")
|
|
4
|
+
return undefined;
|
|
5
|
+
const callee = node.callee;
|
|
6
|
+
if (callee.type === "Identifier") {
|
|
7
|
+
return callee.name === constructorName ? "identifier" : undefined;
|
|
8
|
+
}
|
|
9
|
+
if (callee.type !== "MemberExpression" &&
|
|
10
|
+
callee.type !== "OptionalMemberExpression") {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
// `new url?.URL()` is a syntax error, but `new (url?.URL)(...)` is valid and
|
|
14
|
+
// produces an `OptionalMemberExpression` callee that Babel marks as
|
|
15
|
+
// parenthesized.
|
|
16
|
+
if (callee.type === "OptionalMemberExpression" &&
|
|
17
|
+
!callee.extra?.parenthesized) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
let propertyName = undefined;
|
|
21
|
+
if (callee.computed) {
|
|
22
|
+
if (callee.property.type === "StringLiteral") {
|
|
23
|
+
propertyName = callee.property.value;
|
|
24
|
+
}
|
|
25
|
+
else if (callee.property.type === "TemplateLiteral" &&
|
|
26
|
+
callee.property.expressions.length === 0 &&
|
|
27
|
+
callee.property.quasis.length === 1) {
|
|
28
|
+
const quasi = callee.property.quasis[0];
|
|
29
|
+
const cooked = quasi?.value.cooked;
|
|
30
|
+
propertyName = cooked ?? quasi?.value.raw;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (callee.property.type === "Identifier") {
|
|
34
|
+
propertyName = callee.property.name;
|
|
35
|
+
}
|
|
36
|
+
else if (callee.property.type === "StringLiteral") {
|
|
37
|
+
propertyName = callee.property.value;
|
|
38
|
+
}
|
|
39
|
+
if (propertyName !== constructorName)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (callee.object.type !== "Identifier")
|
|
42
|
+
return undefined;
|
|
43
|
+
if (!isAllowedUrlNamespaceBindingInScope(scope, callee.object.name)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return "member";
|
|
47
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isAllowedUrlModuleImportBinding } from "./is-allowed-url-module-import-binding.js";
|
|
2
|
+
import { isNodeRequireInScope, isRequireFromUrlModule, } from "./is-require-from-url-module.js";
|
|
3
|
+
const doesObjectPatternBindConstructorName = (pattern, constructorName) => {
|
|
4
|
+
for (const property of pattern.properties) {
|
|
5
|
+
if (property.type !== "ObjectProperty")
|
|
6
|
+
continue;
|
|
7
|
+
const keyName = property.computed
|
|
8
|
+
? property.key.type === "StringLiteral"
|
|
9
|
+
? property.key.value
|
|
10
|
+
: undefined
|
|
11
|
+
: property.key.type === "Identifier"
|
|
12
|
+
? property.key.name
|
|
13
|
+
: property.key.type === "StringLiteral"
|
|
14
|
+
? property.key.value
|
|
15
|
+
: undefined;
|
|
16
|
+
if (keyName !== constructorName)
|
|
17
|
+
continue;
|
|
18
|
+
const bindingName = property.value.type === "Identifier"
|
|
19
|
+
? property.value.name
|
|
20
|
+
: property.value.type === "AssignmentPattern" &&
|
|
21
|
+
property.value.left.type === "Identifier"
|
|
22
|
+
? property.value.left.name
|
|
23
|
+
: undefined;
|
|
24
|
+
if (bindingName !== constructorName)
|
|
25
|
+
continue;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
export const isAllowedUrlConstructorInScope = (scope, constructorName) => {
|
|
31
|
+
const binding = scope.getBinding(constructorName);
|
|
32
|
+
if (!binding)
|
|
33
|
+
return true;
|
|
34
|
+
// Aliases like `const { URL: MyURL } = require("node:url")` do not create a
|
|
35
|
+
// binding for `URL`, so they don't affect this shadowing check.
|
|
36
|
+
if (binding.path.isImportSpecifier() &&
|
|
37
|
+
binding.path.parentPath.isImportDeclaration() &&
|
|
38
|
+
(binding.path.node.importKind === "type" ||
|
|
39
|
+
binding.path.parentPath.node.importKind === "type")) {
|
|
40
|
+
// `import type` doesn't introduce a runtime value binding. Treat it as if
|
|
41
|
+
// the constructor name wasn't bound locally (e.g. allow the global binding).
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (isAllowedUrlModuleImportBinding(binding.path, constructorName))
|
|
45
|
+
return true;
|
|
46
|
+
const declarator = binding.path.isVariableDeclarator()
|
|
47
|
+
? binding.path
|
|
48
|
+
: binding.path.findParent((parent) => parent.isVariableDeclarator());
|
|
49
|
+
if (!declarator?.isVariableDeclarator())
|
|
50
|
+
return false;
|
|
51
|
+
const init = declarator.node.init;
|
|
52
|
+
if (declarator.node.id.type === "ObjectPattern") {
|
|
53
|
+
if (!doesObjectPatternBindConstructorName(declarator.node.id, constructorName)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (!isRequireFromUrlModule(init))
|
|
57
|
+
return false;
|
|
58
|
+
if (!isNodeRequireInScope(declarator.scope))
|
|
59
|
+
return false;
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (declarator.node.id.type === "Identifier") {
|
|
63
|
+
if (!isRequireFromUrlModule(init, constructorName))
|
|
64
|
+
return false;
|
|
65
|
+
if (!isNodeRequireInScope(declarator.scope))
|
|
66
|
+
return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const isAllowedUrlModuleImportBinding = (bindingPath, importedName) => {
|
|
2
|
+
// Only allow named imports (e.g. `import { URL } from "node:url"`).
|
|
3
|
+
// Default/namespace imports don't bind the constructor and often indicate
|
|
4
|
+
// interop or module-namespace values, so treating them as "safe stdlib" would
|
|
5
|
+
// create false positives.
|
|
6
|
+
if (!bindingPath.isImportSpecifier())
|
|
7
|
+
return false;
|
|
8
|
+
// `import type { URL } from "node:url"` (and TS 5.0 `import { type URL } ...`)
|
|
9
|
+
// doesn't introduce a runtime value binding, so it can't be treated as a safe
|
|
10
|
+
// stdlib constructor in scope.
|
|
11
|
+
if (bindingPath.node.importKind === "type")
|
|
12
|
+
return false;
|
|
13
|
+
const imported = bindingPath.node.imported;
|
|
14
|
+
const bindingImportedName = imported.type === "Identifier" ? imported.name : imported.value;
|
|
15
|
+
if (bindingImportedName !== importedName)
|
|
16
|
+
return false;
|
|
17
|
+
const importDeclaration = bindingPath.parentPath;
|
|
18
|
+
if (!importDeclaration.isImportDeclaration())
|
|
19
|
+
return false;
|
|
20
|
+
if (importDeclaration.node.importKind === "type")
|
|
21
|
+
return false;
|
|
22
|
+
const source = importDeclaration.node.source.value;
|
|
23
|
+
return source === "url" || source === "node:url";
|
|
24
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isNodeRequireInScope, isRequireFromUrlModule, } from "./is-require-from-url-module.js";
|
|
2
|
+
export const isAllowedUrlNamespaceBindingInScope = (scope, bindingName) => {
|
|
3
|
+
const binding = scope.getBinding(bindingName);
|
|
4
|
+
if (!binding)
|
|
5
|
+
return false;
|
|
6
|
+
if (binding.path.isImportNamespaceSpecifier() ||
|
|
7
|
+
binding.path.isImportDefaultSpecifier()) {
|
|
8
|
+
// Accept both:
|
|
9
|
+
// - `import * as url from "node:url"` (namespace import)
|
|
10
|
+
// - `import url from "node:url"` (CJS interop default import)
|
|
11
|
+
//
|
|
12
|
+
// In Node, `node:url` is a CommonJS module and default import resolves to
|
|
13
|
+
// the module exports object, so it supports `url.URL` / `url.URLSearchParams`.
|
|
14
|
+
// This also matches common transpiled/bundled output shapes.
|
|
15
|
+
const importDeclaration = binding.path.parentPath;
|
|
16
|
+
if (!importDeclaration.isImportDeclaration())
|
|
17
|
+
return false;
|
|
18
|
+
if (importDeclaration.node.importKind === "type")
|
|
19
|
+
return false;
|
|
20
|
+
const source = importDeclaration.node.source.value;
|
|
21
|
+
return source === "url" || source === "node:url";
|
|
22
|
+
}
|
|
23
|
+
const declarator = binding.path.isVariableDeclarator()
|
|
24
|
+
? binding.path
|
|
25
|
+
: binding.path.findParent((parent) => parent.isVariableDeclarator());
|
|
26
|
+
if (!declarator?.isVariableDeclarator())
|
|
27
|
+
return false;
|
|
28
|
+
// No `propertyName` arg: ensure the binding is the module object
|
|
29
|
+
// (`require("node:url")`), not a specific export (`require("node:url").URL`).
|
|
30
|
+
if (!isRequireFromUrlModule(declarator.node.init))
|
|
31
|
+
return false;
|
|
32
|
+
if (!isNodeRequireInScope(declarator.scope))
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Scope } from "@babel/traverse";
|
|
2
|
+
import type { Expression } from "@babel/types";
|
|
3
|
+
/**
|
|
4
|
+
* Returns `true` for:
|
|
5
|
+
* - `require("url")` / `require("node:url")`
|
|
6
|
+
* - `require("url").URL` / `require("node:url").URL` (or any other `propertyName`),
|
|
7
|
+
* but only when the `propertyName` argument is provided
|
|
8
|
+
*
|
|
9
|
+
* Note: This helper only checks the shape of the AST. If `require` is locally
|
|
10
|
+
* bound in the current scope (e.g. by a bundler), callers should additionally
|
|
11
|
+
* verify that `require` refers to Node's `require()` (see
|
|
12
|
+
* {@link isNodeRequireInScope}).
|
|
13
|
+
*/
|
|
14
|
+
export declare const isRequireFromUrlModule: (node: Expression | null | undefined, propertyName?: string) => boolean;
|
|
15
|
+
export declare const isNodeRequireInScope: (scope: Scope) => boolean;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const getRequireFromUrlModuleArgument = (expression) => {
|
|
2
|
+
if (expression.type !== "CallExpression")
|
|
3
|
+
return;
|
|
4
|
+
if (expression.callee.type !== "Identifier")
|
|
5
|
+
return;
|
|
6
|
+
if (expression.callee.name !== "require")
|
|
7
|
+
return;
|
|
8
|
+
if (expression.arguments.length !== 1)
|
|
9
|
+
return;
|
|
10
|
+
const argument = expression.arguments[0];
|
|
11
|
+
if (!argument)
|
|
12
|
+
return;
|
|
13
|
+
if (argument.type === "StringLiteral") {
|
|
14
|
+
if (argument.value === "url")
|
|
15
|
+
return "url";
|
|
16
|
+
if (argument.value === "node:url")
|
|
17
|
+
return "node:url";
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (argument.type === "TemplateLiteral") {
|
|
21
|
+
if (argument.expressions.length > 0)
|
|
22
|
+
return;
|
|
23
|
+
const quasi = argument.quasis[0];
|
|
24
|
+
if (!quasi)
|
|
25
|
+
return;
|
|
26
|
+
const value = quasi.value.cooked ?? quasi.value.raw;
|
|
27
|
+
if (value === "url")
|
|
28
|
+
return "url";
|
|
29
|
+
if (value === "node:url")
|
|
30
|
+
return "node:url";
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Returns `true` for:
|
|
36
|
+
* - `require("url")` / `require("node:url")`
|
|
37
|
+
* - `require("url").URL` / `require("node:url").URL` (or any other `propertyName`),
|
|
38
|
+
* but only when the `propertyName` argument is provided
|
|
39
|
+
*
|
|
40
|
+
* Note: This helper only checks the shape of the AST. If `require` is locally
|
|
41
|
+
* bound in the current scope (e.g. by a bundler), callers should additionally
|
|
42
|
+
* verify that `require` refers to Node's `require()` (see
|
|
43
|
+
* {@link isNodeRequireInScope}).
|
|
44
|
+
*/
|
|
45
|
+
export const isRequireFromUrlModule = (node, propertyName) => {
|
|
46
|
+
if (!node)
|
|
47
|
+
return false;
|
|
48
|
+
if (!propertyName)
|
|
49
|
+
return Boolean(getRequireFromUrlModuleArgument(node));
|
|
50
|
+
if (node.type !== "MemberExpression")
|
|
51
|
+
return false;
|
|
52
|
+
let memberPropertyName = undefined;
|
|
53
|
+
if (node.computed) {
|
|
54
|
+
if (node.property.type === "StringLiteral") {
|
|
55
|
+
memberPropertyName = node.property.value;
|
|
56
|
+
}
|
|
57
|
+
else if (node.property.type === "TemplateLiteral" &&
|
|
58
|
+
node.property.expressions.length === 0 &&
|
|
59
|
+
node.property.quasis.length === 1) {
|
|
60
|
+
const quasi = node.property.quasis[0];
|
|
61
|
+
memberPropertyName = quasi?.value.cooked ?? quasi?.value.raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (node.property.type === "Identifier") {
|
|
65
|
+
memberPropertyName = node.property.name;
|
|
66
|
+
}
|
|
67
|
+
const hasMatchingProperty = memberPropertyName === propertyName;
|
|
68
|
+
if (!hasMatchingProperty)
|
|
69
|
+
return false;
|
|
70
|
+
// We only accept a direct `require("url").X` (or `require("node:url").X`)
|
|
71
|
+
// member expression; deeper chains like `require("url").X.Y` are not relevant
|
|
72
|
+
// for our current usage and would fail the base `require("url")` check anyway.
|
|
73
|
+
if (node.object.type !== "CallExpression")
|
|
74
|
+
return false;
|
|
75
|
+
if (node.object.callee.type !== "Identifier")
|
|
76
|
+
return false;
|
|
77
|
+
if (node.object.callee.name !== "require")
|
|
78
|
+
return false;
|
|
79
|
+
return Boolean(getRequireFromUrlModuleArgument(node.object));
|
|
80
|
+
};
|
|
81
|
+
export const isNodeRequireInScope = (scope) => {
|
|
82
|
+
const requireBinding = scope.getBinding("require");
|
|
83
|
+
if (!requireBinding) {
|
|
84
|
+
const program = scope.getProgramParent().block;
|
|
85
|
+
if (program.type !== "Program")
|
|
86
|
+
return true;
|
|
87
|
+
// Ignore type-only module syntax: it's erased at runtime and commonly used
|
|
88
|
+
// in TS/CJS codebases that still rely on Node's `require`.
|
|
89
|
+
const hasModuleSyntax = program.body.some((statement) => {
|
|
90
|
+
if (statement.type === "ImportDeclaration") {
|
|
91
|
+
return statement.importKind !== "type";
|
|
92
|
+
}
|
|
93
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
94
|
+
return statement.exportKind !== "type";
|
|
95
|
+
}
|
|
96
|
+
if (statement.type === "ExportNamedDeclaration") {
|
|
97
|
+
return statement.exportKind !== "type";
|
|
98
|
+
}
|
|
99
|
+
return statement.type === "ExportDefaultDeclaration";
|
|
100
|
+
});
|
|
101
|
+
// In value-level modules, an unbound `require` is unlikely to be Node's
|
|
102
|
+
// `require` (Node ESM doesn't provide it). Treat it as unsafe to avoid
|
|
103
|
+
// false positives from bundler shims.
|
|
104
|
+
if (hasModuleSyntax)
|
|
105
|
+
return false;
|
|
106
|
+
// In non-module files, assume an unbound `require` is Node's CJS `require`.
|
|
107
|
+
// This can be false for AMD/UMD/browser bundles, but tightening this would
|
|
108
|
+
// introduce false negatives for common Node/CJS inputs.
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (!requireBinding.path.isVariableDeclarator())
|
|
112
|
+
return false;
|
|
113
|
+
const init = requireBinding.path.node.init;
|
|
114
|
+
if (init?.type !== "CallExpression")
|
|
115
|
+
return false;
|
|
116
|
+
if (init.callee.type === "Identifier") {
|
|
117
|
+
const createRequireBinding = scope.getBinding(init.callee.name);
|
|
118
|
+
if (!createRequireBinding)
|
|
119
|
+
return false;
|
|
120
|
+
if (!createRequireBinding.path.isImportSpecifier())
|
|
121
|
+
return false;
|
|
122
|
+
if (createRequireBinding.path.node.importKind === "type")
|
|
123
|
+
return false;
|
|
124
|
+
const imported = createRequireBinding.path.node.imported;
|
|
125
|
+
const importedName = imported.type === "Identifier" ? imported.name : imported.value;
|
|
126
|
+
if (importedName !== "createRequire")
|
|
127
|
+
return false;
|
|
128
|
+
const importDeclaration = createRequireBinding.path.parentPath;
|
|
129
|
+
if (!importDeclaration.isImportDeclaration())
|
|
130
|
+
return false;
|
|
131
|
+
if (importDeclaration.node.importKind === "type")
|
|
132
|
+
return false;
|
|
133
|
+
const source = importDeclaration.node.source.value;
|
|
134
|
+
return source === "module" || source === "node:module";
|
|
135
|
+
}
|
|
136
|
+
if (init.callee.type !== "MemberExpression")
|
|
137
|
+
return false;
|
|
138
|
+
if (init.callee.object.type !== "Identifier")
|
|
139
|
+
return false;
|
|
140
|
+
const propertyName = init.callee.computed
|
|
141
|
+
? init.callee.property.type === "StringLiteral"
|
|
142
|
+
? init.callee.property.value
|
|
143
|
+
: undefined
|
|
144
|
+
: init.callee.property.type === "Identifier"
|
|
145
|
+
? init.callee.property.name
|
|
146
|
+
: undefined;
|
|
147
|
+
if (propertyName !== "createRequire")
|
|
148
|
+
return false;
|
|
149
|
+
const moduleBindingName = init.callee.object.name;
|
|
150
|
+
const moduleBinding = scope.getBinding(moduleBindingName);
|
|
151
|
+
if (!moduleBinding)
|
|
152
|
+
return false;
|
|
153
|
+
const moduleBindingPath = moduleBinding.path;
|
|
154
|
+
if (!moduleBindingPath.isImportDefaultSpecifier() &&
|
|
155
|
+
!moduleBindingPath.isImportNamespaceSpecifier()) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const importDeclaration = moduleBindingPath.parentPath;
|
|
159
|
+
if (!importDeclaration.isImportDeclaration())
|
|
160
|
+
return false;
|
|
161
|
+
const source = importDeclaration.node.source.value;
|
|
162
|
+
return source === "module" || source === "node:module";
|
|
163
|
+
};
|
package/dist/transforms/rename-search-parameters-variables/get-member-expression-property-name.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const getMemberExpressionPropertyName = (expression) => {
|
|
2
|
+
const key = expression.property;
|
|
3
|
+
if (!expression.computed) {
|
|
4
|
+
if (key.type === "Identifier")
|
|
5
|
+
return key.name;
|
|
6
|
+
if (key.type === "StringLiteral")
|
|
7
|
+
return key.value;
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
if (key.type === "StringLiteral")
|
|
11
|
+
return key.value;
|
|
12
|
+
if (key.type === "TemplateLiteral" &&
|
|
13
|
+
key.expressions.length === 0 &&
|
|
14
|
+
key.quasis.length === 1) {
|
|
15
|
+
const quasi = key.quasis[0];
|
|
16
|
+
const cooked = quasi?.value.cooked;
|
|
17
|
+
return cooked ?? quasi?.value.raw;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NodePath } from "@babel/traverse";
|
|
2
|
+
import type { MemberExpression, OptionalMemberExpression } from "@babel/types";
|
|
3
|
+
export declare const getSearchParametersMemberExpression: (referencePath: NodePath) => NodePath<MemberExpression | OptionalMemberExpression> | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const getSearchParametersMemberExpression = (referencePath) => {
|
|
2
|
+
const parent = referencePath.parentPath;
|
|
3
|
+
if (!parent)
|
|
4
|
+
return;
|
|
5
|
+
if (parent.isMemberExpression() &&
|
|
6
|
+
parent.node.object === referencePath.node) {
|
|
7
|
+
return parent;
|
|
8
|
+
}
|
|
9
|
+
if (parent.isOptionalMemberExpression() &&
|
|
10
|
+
parent.node.object === referencePath.node) {
|
|
11
|
+
return parent;
|
|
12
|
+
}
|
|
13
|
+
if (parent.isTSNonNullExpression() &&
|
|
14
|
+
parent.node.expression === referencePath.node) {
|
|
15
|
+
const outer = parent.parentPath;
|
|
16
|
+
if (!outer)
|
|
17
|
+
return;
|
|
18
|
+
if (outer.isMemberExpression() && outer.node.object === parent.node) {
|
|
19
|
+
return outer;
|
|
20
|
+
}
|
|
21
|
+
if (outer.isOptionalMemberExpression() &&
|
|
22
|
+
outer.node.object === parent.node) {
|
|
23
|
+
return outer;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommended": true,
|
|
3
|
+
"notes": "Measured with baseline none: 100.00% of original diff. Added to recommended for readability.",
|
|
4
|
+
"evaluations": {
|
|
5
|
+
"legacy:evaluation": {
|
|
6
|
+
"diffSizePercent": 100,
|
|
7
|
+
"evaluatedAt": "2026-01-25T09:49:29.000Z",
|
|
8
|
+
"changedLines": 0,
|
|
9
|
+
"durationSeconds": 0,
|
|
10
|
+
"stableNames": 0
|
|
11
|
+
},
|
|
12
|
+
"claude-code-2.1.10:claude-code-2.1.11": {
|
|
13
|
+
"diffSizePercent": 100,
|
|
14
|
+
"evaluatedAt": "2026-01-26T07:05:01.592Z",
|
|
15
|
+
"changedLines": 32,
|
|
16
|
+
"durationSeconds": 54.402595083,
|
|
17
|
+
"stableNames": 1358
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
|
|
3
|
+
import { getFilesToProcess, } from "../../core/types.js";
|
|
4
|
+
import { getUrlConstructionKind } from "../get-url-construction-kind.js";
|
|
5
|
+
import { isAllowedUrlConstructorInScope } from "../is-allowed-url-constructor-in-scope.js";
|
|
6
|
+
import { isSearchParametersDestructureUsage, isSearchParametersForOfUsage, isSearchParametersPropertyAccess, isSearchParametersSpreadUsage, } from "./search-parameters-usage-heuristics.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
9
|
+
const traverse = require("@babel/traverse").default;
|
|
10
|
+
export const renameSearchParametersVariablesTransform = {
|
|
11
|
+
id: "rename-search-parameters-variables",
|
|
12
|
+
description: "Renames variables initialized with new URLSearchParams(...) when they’re used (member access, destructuring, for-of, spread)",
|
|
13
|
+
scope: "file",
|
|
14
|
+
parallelizable: true,
|
|
15
|
+
transform(context) {
|
|
16
|
+
let nodesVisited = 0;
|
|
17
|
+
let transformationsApplied = 0;
|
|
18
|
+
for (const fileInfo of getFilesToProcess(context)) {
|
|
19
|
+
const group = new RenameGroup();
|
|
20
|
+
traverse(fileInfo.ast, {
|
|
21
|
+
VariableDeclarator(path) {
|
|
22
|
+
nodesVisited += 1;
|
|
23
|
+
// Only rename actual bindings. Patterns don't create a stable
|
|
24
|
+
// `URLSearchParams` variable to rename, and `URLSearchParams` is
|
|
25
|
+
// iterable (not a plain object), so destructuring doesn't map cleanly.
|
|
26
|
+
if (path.node.id.type !== "Identifier")
|
|
27
|
+
return;
|
|
28
|
+
const constructionKind = getUrlConstructionKind(path.scope, path.node.init, "URLSearchParams");
|
|
29
|
+
if (!constructionKind)
|
|
30
|
+
return;
|
|
31
|
+
if (constructionKind === "identifier" &&
|
|
32
|
+
!isAllowedUrlConstructorInScope(path.scope, "URLSearchParams")) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const currentName = path.node.id.name;
|
|
36
|
+
if (isStableRenamed(currentName))
|
|
37
|
+
return;
|
|
38
|
+
const binding = path.scope.getBinding(currentName);
|
|
39
|
+
if (!binding)
|
|
40
|
+
return;
|
|
41
|
+
if (!binding.constant)
|
|
42
|
+
return;
|
|
43
|
+
// Unlike `URL`, `URLSearchParams` is frequently used via a single
|
|
44
|
+
// method/property call. Construction is already a strong signal, so we
|
|
45
|
+
// rename on first evidence of usage.
|
|
46
|
+
let hasUsage = false;
|
|
47
|
+
for (const referencePath of binding.referencePaths) {
|
|
48
|
+
if (isSearchParametersPropertyAccess(referencePath) ||
|
|
49
|
+
isSearchParametersForOfUsage(referencePath) ||
|
|
50
|
+
isSearchParametersSpreadUsage(referencePath) ||
|
|
51
|
+
isSearchParametersDestructureUsage(referencePath)) {
|
|
52
|
+
hasUsage = true;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!hasUsage)
|
|
57
|
+
return;
|
|
58
|
+
const candidateName = "searchParameters";
|
|
59
|
+
if (candidateName === currentName)
|
|
60
|
+
return;
|
|
61
|
+
group.add({
|
|
62
|
+
scope: path.scope,
|
|
63
|
+
currentName,
|
|
64
|
+
baseName: candidateName,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
transformationsApplied += group.apply();
|
|
69
|
+
}
|
|
70
|
+
return Promise.resolve({
|
|
71
|
+
nodesVisited,
|
|
72
|
+
transformationsApplied,
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
package/dist/transforms/rename-search-parameters-variables/search-parameters-usage-heuristics.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NodePath } from "@babel/traverse";
|
|
2
|
+
export declare const isSearchParametersPropertyAccess: (referencePath: NodePath) => boolean;
|
|
3
|
+
export declare const isSearchParametersForOfUsage: (referencePath: NodePath) => boolean;
|
|
4
|
+
export declare const isSearchParametersSpreadUsage: (referencePath: NodePath) => boolean;
|
|
5
|
+
export declare const isSearchParametersDestructureUsage: (referencePath: NodePath) => boolean;
|
package/dist/transforms/rename-search-parameters-variables/search-parameters-usage-heuristics.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { getMemberExpressionPropertyName } from "./get-member-expression-property-name.js";
|
|
2
|
+
import { getSearchParametersMemberExpression } from "./get-search-parameters-member-expression.js";
|
|
3
|
+
const searchParametersPropertyNames = new Set([
|
|
4
|
+
"append",
|
|
5
|
+
"delete",
|
|
6
|
+
"entries",
|
|
7
|
+
"forEach",
|
|
8
|
+
"get",
|
|
9
|
+
"getAll",
|
|
10
|
+
"has",
|
|
11
|
+
"keys",
|
|
12
|
+
"set",
|
|
13
|
+
"size",
|
|
14
|
+
"sort",
|
|
15
|
+
"toString",
|
|
16
|
+
"values",
|
|
17
|
+
]);
|
|
18
|
+
export const isSearchParametersPropertyAccess = (referencePath) => {
|
|
19
|
+
const memberExpression = getSearchParametersMemberExpression(referencePath);
|
|
20
|
+
if (!memberExpression)
|
|
21
|
+
return false;
|
|
22
|
+
const keyName = getMemberExpressionPropertyName(memberExpression.node);
|
|
23
|
+
if (!keyName)
|
|
24
|
+
return false;
|
|
25
|
+
return searchParametersPropertyNames.has(keyName);
|
|
26
|
+
};
|
|
27
|
+
export const isSearchParametersForOfUsage = (referencePath) => {
|
|
28
|
+
const parent = referencePath.parentPath;
|
|
29
|
+
if (!parent)
|
|
30
|
+
return false;
|
|
31
|
+
if (parent.isForOfStatement()) {
|
|
32
|
+
return parent.node.right === referencePath.node;
|
|
33
|
+
}
|
|
34
|
+
if (parent.isTSNonNullExpression() &&
|
|
35
|
+
parent.node.expression === referencePath.node) {
|
|
36
|
+
const forOf = parent.parentPath;
|
|
37
|
+
if (!forOf)
|
|
38
|
+
return false;
|
|
39
|
+
if (!forOf.isForOfStatement())
|
|
40
|
+
return false;
|
|
41
|
+
return forOf.node.right === parent.node;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
export const isSearchParametersSpreadUsage = (referencePath) => {
|
|
46
|
+
const parent = referencePath.parentPath;
|
|
47
|
+
if (!parent)
|
|
48
|
+
return false;
|
|
49
|
+
if (parent.isSpreadElement()) {
|
|
50
|
+
const container = parent.parentPath;
|
|
51
|
+
if (!container)
|
|
52
|
+
return false;
|
|
53
|
+
if (!container.isArrayExpression() &&
|
|
54
|
+
!container.isCallExpression() &&
|
|
55
|
+
!container.isNewExpression()) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return parent.node.argument === referencePath.node;
|
|
59
|
+
}
|
|
60
|
+
if (parent.isTSNonNullExpression() &&
|
|
61
|
+
parent.node.expression === referencePath.node) {
|
|
62
|
+
const spread = parent.parentPath;
|
|
63
|
+
if (!spread)
|
|
64
|
+
return false;
|
|
65
|
+
if (!spread.isSpreadElement())
|
|
66
|
+
return false;
|
|
67
|
+
const container = spread.parentPath;
|
|
68
|
+
if (!container)
|
|
69
|
+
return false;
|
|
70
|
+
if (!container.isArrayExpression() &&
|
|
71
|
+
!container.isCallExpression() &&
|
|
72
|
+
!container.isNewExpression()) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return spread.node.argument === parent.node;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
};
|
|
79
|
+
const isSearchParametersDestructurePattern = (pattern) => {
|
|
80
|
+
if (pattern.properties.length === 0)
|
|
81
|
+
return false;
|
|
82
|
+
for (const property of pattern.properties) {
|
|
83
|
+
// Rest properties suggest generic object destructuring, so we stay
|
|
84
|
+
// conservative and don't treat it as URLSearchParams usage evidence.
|
|
85
|
+
if (property.type === "RestElement")
|
|
86
|
+
return false;
|
|
87
|
+
const keyName = property.computed
|
|
88
|
+
? property.key.type === "StringLiteral"
|
|
89
|
+
? property.key.value
|
|
90
|
+
: undefined
|
|
91
|
+
: property.key.type === "Identifier"
|
|
92
|
+
? property.key.name
|
|
93
|
+
: property.key.type === "StringLiteral"
|
|
94
|
+
? property.key.value
|
|
95
|
+
: undefined;
|
|
96
|
+
if (!keyName)
|
|
97
|
+
return false;
|
|
98
|
+
// Be strict: `URLSearchParams` method names overlap with many other APIs, so
|
|
99
|
+
// we only treat destructuring as usage evidence when *every* key matches.
|
|
100
|
+
if (!searchParametersPropertyNames.has(keyName))
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
export const isSearchParametersDestructureUsage = (referencePath) => {
|
|
106
|
+
const parent = referencePath.parentPath;
|
|
107
|
+
if (parent?.isTSNonNullExpression() &&
|
|
108
|
+
parent.node.expression === referencePath.node) {
|
|
109
|
+
const outer = parent.parentPath;
|
|
110
|
+
if (!outer)
|
|
111
|
+
return false;
|
|
112
|
+
if (outer.isVariableDeclarator()) {
|
|
113
|
+
if (outer.node.init !== parent.node)
|
|
114
|
+
return false;
|
|
115
|
+
const id = outer.node.id;
|
|
116
|
+
if (id.type !== "ObjectPattern")
|
|
117
|
+
return false;
|
|
118
|
+
return isSearchParametersDestructurePattern(id);
|
|
119
|
+
}
|
|
120
|
+
if (outer.isAssignmentExpression()) {
|
|
121
|
+
if (outer.node.operator !== "=")
|
|
122
|
+
return false;
|
|
123
|
+
if (outer.node.right !== parent.node)
|
|
124
|
+
return false;
|
|
125
|
+
const left = outer.node.left;
|
|
126
|
+
if (left.type !== "ObjectPattern")
|
|
127
|
+
return false;
|
|
128
|
+
return isSearchParametersDestructurePattern(left);
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
if (parent?.isVariableDeclarator()) {
|
|
133
|
+
if (parent.node.init !== referencePath.node)
|
|
134
|
+
return false;
|
|
135
|
+
const id = parent.node.id;
|
|
136
|
+
if (id.type !== "ObjectPattern")
|
|
137
|
+
return false;
|
|
138
|
+
return isSearchParametersDestructurePattern(id);
|
|
139
|
+
}
|
|
140
|
+
if (parent?.isAssignmentExpression()) {
|
|
141
|
+
if (parent.node.operator !== "=")
|
|
142
|
+
return false;
|
|
143
|
+
if (parent.node.right !== referencePath.node)
|
|
144
|
+
return false;
|
|
145
|
+
const left = parent.node.left;
|
|
146
|
+
if (left.type !== "ObjectPattern")
|
|
147
|
+
return false;
|
|
148
|
+
return isSearchParametersDestructurePattern(left);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
};
|
|
@@ -2,17 +2,11 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
|
|
3
3
|
import { getFilesToProcess, } from "../../core/types.js";
|
|
4
4
|
import { hasUrlDestructure, urlPropertyNames, } from "../url-usage-heuristics.js";
|
|
5
|
+
import { getUrlConstructionKind } from "../get-url-construction-kind.js";
|
|
6
|
+
import { isAllowedUrlConstructorInScope } from "../is-allowed-url-constructor-in-scope.js";
|
|
5
7
|
const require = createRequire(import.meta.url);
|
|
6
8
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
7
9
|
const traverse = require("@babel/traverse").default;
|
|
8
|
-
const isUrlConstruction = (node) => {
|
|
9
|
-
if (node?.type !== "NewExpression")
|
|
10
|
-
return false;
|
|
11
|
-
if (node.callee.type !== "Identifier" || node.callee.name !== "URL") {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
return true;
|
|
15
|
-
};
|
|
16
10
|
const isUrlPropertyAccess = (referencePath) => {
|
|
17
11
|
const parent = referencePath.parentPath;
|
|
18
12
|
if (!parent)
|
|
@@ -48,14 +42,21 @@ export const renameUrlVariablesTransform = {
|
|
|
48
42
|
nodesVisited += 1;
|
|
49
43
|
if (path.node.id.type !== "Identifier")
|
|
50
44
|
return;
|
|
51
|
-
|
|
45
|
+
const constructionKind = getUrlConstructionKind(path.scope, path.node.init, "URL");
|
|
46
|
+
if (!constructionKind)
|
|
47
|
+
return;
|
|
48
|
+
if (constructionKind === "identifier" &&
|
|
49
|
+
!isAllowedUrlConstructorInScope(path.scope, "URL")) {
|
|
52
50
|
return;
|
|
51
|
+
}
|
|
53
52
|
const currentName = path.node.id.name;
|
|
54
53
|
if (isStableRenamed(currentName))
|
|
55
54
|
return;
|
|
56
55
|
const binding = path.scope.getBinding(currentName);
|
|
57
56
|
if (!binding)
|
|
58
57
|
return;
|
|
58
|
+
if (!binding.constant)
|
|
59
|
+
return;
|
|
59
60
|
let sawUrlDestructure = false;
|
|
60
61
|
let propertyAccesses = 0;
|
|
61
62
|
for (const referencePath of binding.referencePaths) {
|
|
@@ -71,12 +72,12 @@ export const renameUrlVariablesTransform = {
|
|
|
71
72
|
}
|
|
72
73
|
if (!sawUrlDestructure && propertyAccesses < 2)
|
|
73
74
|
return;
|
|
75
|
+
// Name collisions (including unresolved program globals) are handled by
|
|
76
|
+
// `RenameGroup`/`findAvailableName` so we don't need to bail out early
|
|
77
|
+
// when `url` is already referenced elsewhere in the file.
|
|
74
78
|
const candidateName = "url";
|
|
75
79
|
if (candidateName === currentName)
|
|
76
80
|
return;
|
|
77
|
-
const programScope = path.scope.getProgramParent();
|
|
78
|
-
if (Object.hasOwn(programScope.globals, candidateName))
|
|
79
|
-
return;
|
|
80
81
|
group.add({
|
|
81
82
|
scope: path.scope,
|
|
82
83
|
currentName,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "miniread",
|
|
3
3
|
"author": "Łukasz Jerciński",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.53.0",
|
|
6
6
|
"description": "Transform minified JavaScript/TypeScript into a more readable form using deterministic AST-based transforms.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|