miniread 1.87.0 → 1.88.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/transforms/_generated/manifest.js +9 -3
- package/dist/transforms/_generated/registry.js +2 -0
- package/dist/transforms/preset-stats.json +2 -2
- package/dist/transforms/recommended-transform-order.d.ts +1 -1
- package/dist/transforms/recommended-transform-order.js +1 -1
- package/dist/transforms/rename-string-split-variables/manifest.json +3 -2
- package/dist/transforms/rename-string-split-variables-v2/detect-split-result-candidate.d.ts +7 -0
- package/dist/transforms/rename-string-split-variables-v2/detect-split-result-candidate.js +117 -0
- package/dist/transforms/rename-string-split-variables-v2/get-split-helper-info.d.ts +8 -0
- package/dist/transforms/rename-string-split-variables-v2/get-split-helper-info.js +40 -0
- package/dist/transforms/rename-string-split-variables-v2/has-array-like-split-usage.d.ts +2 -0
- package/dist/transforms/rename-string-split-variables-v2/has-array-like-split-usage.js +70 -0
- package/dist/transforms/rename-string-split-variables-v2/is-runtime-boolean-shadowed.d.ts +2 -0
- package/dist/transforms/rename-string-split-variables-v2/is-runtime-boolean-shadowed.js +46 -0
- package/dist/transforms/rename-string-split-variables-v2/manifest.json +13 -0
- package/dist/transforms/rename-string-split-variables-v2/rename-string-split-variables-v2-transform.d.ts +2 -0
- package/dist/transforms/rename-string-split-variables-v2/rename-string-split-variables-v2-transform.js +116 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-declarations.d.ts +11 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-declarations.js +80 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-guards.d.ts +8 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-guards.js +73 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-loop.d.ts +3 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-loop.js +116 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-expressions.d.ts +3 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-expressions.js +86 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-tests.d.ts +4 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-tests.js +39 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail.d.ts +3 -0
- package/dist/transforms/rename-string-split-variables-v2/split-helper-tail.js +49 -0
- package/dist/transforms/rename-string-split-variables-v2/unwrap-transparent-expression.d.ts +2 -0
- package/dist/transforms/rename-string-split-variables-v2/unwrap-transparent-expression.js +22 -0
- package/package.json +1 -1
|
@@ -320,10 +320,16 @@ const manifestData = {
|
|
|
320
320
|
evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-02-06T14:45:47.110Z", "changedLines": 44, "durationSeconds": 167.840422062, "stableNames": 1358 } },
|
|
321
321
|
},
|
|
322
322
|
"rename-string-split-variables": {
|
|
323
|
-
recommended:
|
|
324
|
-
notes: "
|
|
323
|
+
recommended: false,
|
|
324
|
+
notes: "Superseded by rename-string-split-variables-v2, which also handles direct split results.",
|
|
325
|
+
supersededBy: "rename-string-split-variables-v2",
|
|
325
326
|
evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-01-27T06:41:49.624Z", "changedLines": 0, "durationSeconds": 149.03264720899998, "stableNames": 1357 } },
|
|
326
327
|
},
|
|
328
|
+
"rename-string-split-variables-v2": {
|
|
329
|
+
recommended: true,
|
|
330
|
+
notes: "Extends manual split helper renames to include direct split results. v2 helper modules are intentionally self-contained to match the project convention for versioned transforms.",
|
|
331
|
+
evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 99.99811516350957, "evaluatedAt": "2026-02-06T14:53:55.285Z", "changedLines": 1197, "durationSeconds": 209.837459189, "stableNames": 1359 } },
|
|
332
|
+
},
|
|
327
333
|
"rename-this-aliases": {
|
|
328
334
|
recommended: true,
|
|
329
335
|
notes: "Measured with baseline none: 0.00%. Improves readability by stabilizing `this` aliases used for closures.",
|
|
@@ -526,7 +532,7 @@ export const recommendedTransformIds = [
|
|
|
526
532
|
"rename-search-parameters-variables",
|
|
527
533
|
"rename-setstate-updater-parameters",
|
|
528
534
|
"rename-file-extension-variables",
|
|
529
|
-
"rename-string-split-variables",
|
|
535
|
+
"rename-string-split-variables-v2",
|
|
530
536
|
"rename-this-aliases",
|
|
531
537
|
"rename-timeout-promises",
|
|
532
538
|
"rename-timeout-ids",
|
|
@@ -67,6 +67,7 @@ import { renameSafePropertyAccessorParametersTransform } from "../rename-safe-pr
|
|
|
67
67
|
import { renameSearchParametersVariablesTransform } from "../rename-search-parameters-variables/rename-search-parameters-variables-transform.js";
|
|
68
68
|
import { renameSetstateUpdaterParametersTransform } from "../rename-setstate-updater-parameters/rename-setstate-updater-parameters-transform.js";
|
|
69
69
|
import { renameStringSplitVariablesTransform } from "../rename-string-split-variables/rename-string-split-variables-transform.js";
|
|
70
|
+
import { renameStringSplitVariablesV2Transform } from "../rename-string-split-variables-v2/rename-string-split-variables-v2-transform.js";
|
|
70
71
|
import { renameThisAliasesTransform } from "../rename-this-aliases/rename-this-aliases-transform.js";
|
|
71
72
|
import { renameTimeoutDurationParametersTransform } from "../rename-timeout-duration-parameters/rename-timeout-duration-parameters-transform.js";
|
|
72
73
|
import { renameTimeoutIdsTransform } from "../rename-timeout-ids/rename-timeout-ids-transform.js";
|
|
@@ -161,6 +162,7 @@ export const transformRegistry = {
|
|
|
161
162
|
[renameSearchParametersVariablesTransform.id]: renameSearchParametersVariablesTransform,
|
|
162
163
|
[renameSetstateUpdaterParametersTransform.id]: renameSetstateUpdaterParametersTransform,
|
|
163
164
|
[renameStringSplitVariablesTransform.id]: renameStringSplitVariablesTransform,
|
|
165
|
+
[renameStringSplitVariablesV2Transform.id]: renameStringSplitVariablesV2Transform,
|
|
164
166
|
[renameThisAliasesTransform.id]: renameThisAliasesTransform,
|
|
165
167
|
[renameTimeoutDurationParametersTransform.id]: renameTimeoutDurationParametersTransform,
|
|
166
168
|
[renameTimeoutIdsTransform.id]: renameTimeoutIdsTransform,
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* This lets us tune transform interactions intentionally instead of relying on
|
|
5
5
|
* alphabetical ID sorting.
|
|
6
6
|
*/
|
|
7
|
-
export declare const recommendedTransformOrder: readonly ["stabilize-top-level-bindings", "stabilize-nested-bindings", "stabilize-deferred-top-level-bindings", "stabilize-deferred-stable-rhs", "expand-boolean-literals", "expand-sequence-expressions-v5", "expand-special-number-literals", "expand-typeof-undefined-comparisons", "expand-undefined-literals", "remove-redundant-else", "rename-arguments-length-flags", "rename-asap-wrappers", "rename-awaiter-parameters", "rename-awaiter-helper-functions", "rename-buffer-variables", "rename-to-buffer-results", "rename-catch-parameters", "rename-promise-catch-parameters", "rename-char-code-at", "rename-charcode-variables-v2", "rename-client-aliases", "rename-comparison-flags", "rename-platform-win32-flags", "rename-date-now-start-times", "rename-default-options-parameters-v2", "rename-default-options-parameters-v3", "rename-deferred-resolve-parameters", "rename-deferred-resolve-parameters-v2", "rename-destructured-aliases", "rename-rest-parameters", "rename-rest-pop-callbacks", "rename-execfile-arguments", "rename-file-reader-variables", "rename-error-first-callback-parameters", "rename-error-first-callback-parameters-v2", "rename-error-variables", "rename-event-parameters", "rename-add-event-listener-parameters", "rename-http-server-parameters", "rename-fs-sync-variables", "rename-http-method-parameters", "rename-interval-ids", "rename-indexeddb-request-variables", "rename-loop-index-variables-v3", "rename-queue-traversal-variables", "rename-loop-length-variables", "rename-document-fragment-variables", "rename-object-keys-variables", "rename-object-keys-iterator-variables", "rename-object-keys-reducer-parameters", "rename-object-entries-parameters", "rename-parameters-to-match-properties-v2", "rename-invalid-parameter-arguments", "rename-promise-executor-parameters-v2", "rename-range-parameters", "rename-read-file-lines", "rename-regex-builders", "rename-regex-source-parameters", "rename-search-parameters-variables", "rename-setstate-updater-parameters", "rename-file-extension-variables", "rename-string-split-variables", "rename-this-aliases", "rename-timeout-promises", "rename-timeout-ids", "rename-typeof-variables", "rename-typescript-helper-aliases", "rename-uint8array-concat-variables", "rename-url-parameters", "rename-url-variables", "rename-use-reference-guards-v2", "rename-use-reference-sets", "rename-worker-handles", "rename-zod-check-parameters", "simplify-boolean-negations", "simplify-string-trim", "split-variable-declarations", "use-optional-chaining", "use-object-property-shorthand", "use-object-shorthand", "replace-dynamic-require-eval"];
|
|
7
|
+
export declare const recommendedTransformOrder: readonly ["stabilize-top-level-bindings", "stabilize-nested-bindings", "stabilize-deferred-top-level-bindings", "stabilize-deferred-stable-rhs", "expand-boolean-literals", "expand-sequence-expressions-v5", "expand-special-number-literals", "expand-typeof-undefined-comparisons", "expand-undefined-literals", "remove-redundant-else", "rename-arguments-length-flags", "rename-asap-wrappers", "rename-awaiter-parameters", "rename-awaiter-helper-functions", "rename-buffer-variables", "rename-to-buffer-results", "rename-catch-parameters", "rename-promise-catch-parameters", "rename-char-code-at", "rename-charcode-variables-v2", "rename-client-aliases", "rename-comparison-flags", "rename-platform-win32-flags", "rename-date-now-start-times", "rename-default-options-parameters-v2", "rename-default-options-parameters-v3", "rename-deferred-resolve-parameters", "rename-deferred-resolve-parameters-v2", "rename-destructured-aliases", "rename-rest-parameters", "rename-rest-pop-callbacks", "rename-execfile-arguments", "rename-file-reader-variables", "rename-error-first-callback-parameters", "rename-error-first-callback-parameters-v2", "rename-error-variables", "rename-event-parameters", "rename-add-event-listener-parameters", "rename-http-server-parameters", "rename-fs-sync-variables", "rename-http-method-parameters", "rename-interval-ids", "rename-indexeddb-request-variables", "rename-loop-index-variables-v3", "rename-queue-traversal-variables", "rename-loop-length-variables", "rename-document-fragment-variables", "rename-object-keys-variables", "rename-object-keys-iterator-variables", "rename-object-keys-reducer-parameters", "rename-object-entries-parameters", "rename-parameters-to-match-properties-v2", "rename-invalid-parameter-arguments", "rename-promise-executor-parameters-v2", "rename-range-parameters", "rename-read-file-lines", "rename-regex-builders", "rename-regex-source-parameters", "rename-search-parameters-variables", "rename-setstate-updater-parameters", "rename-file-extension-variables", "rename-string-split-variables-v2", "rename-this-aliases", "rename-timeout-promises", "rename-timeout-ids", "rename-typeof-variables", "rename-typescript-helper-aliases", "rename-uint8array-concat-variables", "rename-url-parameters", "rename-url-variables", "rename-use-reference-guards-v2", "rename-use-reference-sets", "rename-worker-handles", "rename-zod-check-parameters", "simplify-boolean-negations", "simplify-string-trim", "split-variable-declarations", "use-optional-chaining", "use-object-property-shorthand", "use-object-shorthand", "replace-dynamic-require-eval"];
|
|
@@ -78,7 +78,7 @@ export const recommendedTransformOrder = [
|
|
|
78
78
|
"rename-search-parameters-variables",
|
|
79
79
|
"rename-setstate-updater-parameters",
|
|
80
80
|
"rename-file-extension-variables",
|
|
81
|
-
"rename-string-split-variables",
|
|
81
|
+
"rename-string-split-variables-v2",
|
|
82
82
|
"rename-this-aliases",
|
|
83
83
|
"rename-timeout-promises",
|
|
84
84
|
"rename-timeout-ids",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"recommended":
|
|
2
|
+
"recommended": false,
|
|
3
|
+
"supersededBy": "rename-string-split-variables-v2",
|
|
3
4
|
"evaluations": {
|
|
4
5
|
"claude-code-2.1.10:claude-code-2.1.11": {
|
|
5
6
|
"diffSizePercent": 100,
|
|
@@ -9,5 +10,5 @@
|
|
|
9
10
|
"stableNames": 1357
|
|
10
11
|
}
|
|
11
12
|
},
|
|
12
|
-
"notes": "
|
|
13
|
+
"notes": "Superseded by rename-string-split-variables-v2, which also handles direct split results."
|
|
13
14
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VariableDeclarator } from "@babel/types";
|
|
2
|
+
type SplitResultCandidate = {
|
|
3
|
+
currentName: string;
|
|
4
|
+
requiresGlobalBoolean: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const detectSplitResultCandidate: (node: VariableDeclarator) => SplitResultCandidate | undefined;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { getUnwrappedCallExpression } from "./unwrap-transparent-expression.js";
|
|
2
|
+
const isAllowedSplitDelimiter = (expression) => {
|
|
3
|
+
return (expression.type === "StringLiteral" ||
|
|
4
|
+
expression.type === "RegExpLiteral" ||
|
|
5
|
+
expression.type === "Identifier");
|
|
6
|
+
};
|
|
7
|
+
const isAllowedSplitLimit = (expression) => {
|
|
8
|
+
if (expression.type === "NumericLiteral")
|
|
9
|
+
return true;
|
|
10
|
+
if (expression.type === "Identifier")
|
|
11
|
+
return true;
|
|
12
|
+
if (expression.type !== "UnaryExpression")
|
|
13
|
+
return false;
|
|
14
|
+
if (expression.operator !== "+" && expression.operator !== "-")
|
|
15
|
+
return false;
|
|
16
|
+
return expression.argument.type === "NumericLiteral";
|
|
17
|
+
};
|
|
18
|
+
const getMemberProperty = (callee) => {
|
|
19
|
+
if (callee.type !== "MemberExpression" &&
|
|
20
|
+
callee.type !== "OptionalMemberExpression") {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
return callee;
|
|
24
|
+
};
|
|
25
|
+
const isSplitMember = (member) => {
|
|
26
|
+
const property = member.property;
|
|
27
|
+
if (property.type === "Identifier") {
|
|
28
|
+
if (member.computed)
|
|
29
|
+
return false;
|
|
30
|
+
return property.name === "split";
|
|
31
|
+
}
|
|
32
|
+
if (property.type === "StringLiteral") {
|
|
33
|
+
if (!member.computed)
|
|
34
|
+
return false;
|
|
35
|
+
return property.value === "split";
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
};
|
|
39
|
+
const hasSupportedSplitArguments = (node) => {
|
|
40
|
+
if (node.arguments.length > 2)
|
|
41
|
+
return false;
|
|
42
|
+
if (node.arguments.length === 0)
|
|
43
|
+
return true;
|
|
44
|
+
const [firstArgument] = node.arguments;
|
|
45
|
+
if (!firstArgument ||
|
|
46
|
+
firstArgument.type === "SpreadElement" ||
|
|
47
|
+
firstArgument.type === "ArgumentPlaceholder") {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (!isAllowedSplitDelimiter(firstArgument))
|
|
51
|
+
return false;
|
|
52
|
+
const secondArgument = node.arguments[1];
|
|
53
|
+
if (!secondArgument)
|
|
54
|
+
return true;
|
|
55
|
+
if (secondArgument.type === "SpreadElement" ||
|
|
56
|
+
secondArgument.type === "ArgumentPlaceholder") {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return isAllowedSplitLimit(secondArgument);
|
|
60
|
+
};
|
|
61
|
+
const isSplitCall = (node) => {
|
|
62
|
+
const member = getMemberProperty(node.callee);
|
|
63
|
+
if (!member)
|
|
64
|
+
return false;
|
|
65
|
+
if (!isSplitMember(member))
|
|
66
|
+
return false;
|
|
67
|
+
return hasSupportedSplitArguments(node);
|
|
68
|
+
};
|
|
69
|
+
const isFilterBooleanCall = (node) => {
|
|
70
|
+
const member = getMemberProperty(node.callee);
|
|
71
|
+
if (!member)
|
|
72
|
+
return false;
|
|
73
|
+
const property = member.property;
|
|
74
|
+
if (member.computed && property.type !== "StringLiteral")
|
|
75
|
+
return false;
|
|
76
|
+
if (!member.computed && property.type !== "Identifier")
|
|
77
|
+
return false;
|
|
78
|
+
if (property.type === "Identifier" && property.name !== "filter")
|
|
79
|
+
return false;
|
|
80
|
+
if (property.type === "StringLiteral" && property.value !== "filter")
|
|
81
|
+
return false;
|
|
82
|
+
if (node.arguments.length !== 1)
|
|
83
|
+
return false;
|
|
84
|
+
const [predicate] = node.arguments;
|
|
85
|
+
if (!predicate)
|
|
86
|
+
return false;
|
|
87
|
+
return predicate.type === "Identifier" && predicate.name === "Boolean";
|
|
88
|
+
};
|
|
89
|
+
export const detectSplitResultCandidate = (node) => {
|
|
90
|
+
if (node.id.type !== "Identifier")
|
|
91
|
+
return undefined;
|
|
92
|
+
const init = getUnwrappedCallExpression(node.init);
|
|
93
|
+
if (!init)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (isSplitCall(init)) {
|
|
96
|
+
return {
|
|
97
|
+
currentName: node.id.name,
|
|
98
|
+
requiresGlobalBoolean: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (isFilterBooleanCall(init) &&
|
|
102
|
+
(init.callee.type === "MemberExpression" ||
|
|
103
|
+
init.callee.type === "OptionalMemberExpression")) {
|
|
104
|
+
if (init.callee.object.type === "Super")
|
|
105
|
+
return undefined;
|
|
106
|
+
const filterTarget = getUnwrappedCallExpression(init.callee.object);
|
|
107
|
+
if (!filterTarget)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (!isSplitCall(filterTarget))
|
|
110
|
+
return undefined;
|
|
111
|
+
return {
|
|
112
|
+
currentName: node.id.name,
|
|
113
|
+
requiresGlobalBoolean: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ArrowFunctionExpression, FunctionDeclaration, FunctionExpression } from "@babel/types";
|
|
2
|
+
import type { SplitHelperDeclarations } from "./split-helper-declarations.js";
|
|
3
|
+
type SplitHelperInfo = SplitHelperDeclarations & {
|
|
4
|
+
inputName: string;
|
|
5
|
+
delimiterName: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const getSplitHelperInfo: (node: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression) => SplitHelperInfo | undefined;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isIdentifierNode } from "./split-helper-guards.js";
|
|
2
|
+
import { getSplitHelperDeclarationsFromStatements } from "./split-helper-declarations.js";
|
|
3
|
+
import { isSplitHelperLoop } from "./split-helper-loop.js";
|
|
4
|
+
import { isSplitHelperTail } from "./split-helper-tail.js";
|
|
5
|
+
export const getSplitHelperInfo = (node) => {
|
|
6
|
+
if (node.params.length !== 2)
|
|
7
|
+
return undefined;
|
|
8
|
+
const inputParameter = node.params[0];
|
|
9
|
+
const delimiterParameter = node.params[1];
|
|
10
|
+
if (!isIdentifierNode(inputParameter) ||
|
|
11
|
+
!isIdentifierNode(delimiterParameter))
|
|
12
|
+
return undefined;
|
|
13
|
+
if (node.body.type !== "BlockStatement")
|
|
14
|
+
return undefined;
|
|
15
|
+
const statements = node.body.body;
|
|
16
|
+
const declarationsMatch = getSplitHelperDeclarationsFromStatements(statements, inputParameter.name, delimiterParameter.name);
|
|
17
|
+
if (!declarationsMatch)
|
|
18
|
+
return undefined;
|
|
19
|
+
const { nextStatementIndex, ...declarations } = declarationsMatch;
|
|
20
|
+
const loopStatement = statements[nextStatementIndex];
|
|
21
|
+
if (!isSplitHelperLoop(loopStatement, declarations, inputParameter.name, delimiterParameter.name))
|
|
22
|
+
return undefined;
|
|
23
|
+
const tailStatements = statements.slice(nextStatementIndex + 1);
|
|
24
|
+
if (!isSplitHelperTail(tailStatements, declarations, inputParameter.name))
|
|
25
|
+
return undefined;
|
|
26
|
+
const uniqueNames = new Set([
|
|
27
|
+
inputParameter.name,
|
|
28
|
+
delimiterParameter.name,
|
|
29
|
+
declarations.partsName,
|
|
30
|
+
declarations.startIndexName,
|
|
31
|
+
declarations.delimiterIndexName,
|
|
32
|
+
]);
|
|
33
|
+
if (uniqueNames.size !== 5)
|
|
34
|
+
return undefined;
|
|
35
|
+
return {
|
|
36
|
+
inputName: inputParameter.name,
|
|
37
|
+
delimiterName: delimiterParameter.name,
|
|
38
|
+
...declarations,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const ARRAY_USAGE_MEMBER_NAMES = new Set([
|
|
2
|
+
"length",
|
|
3
|
+
"join",
|
|
4
|
+
"slice",
|
|
5
|
+
"map",
|
|
6
|
+
"filter",
|
|
7
|
+
"find",
|
|
8
|
+
"some",
|
|
9
|
+
"every",
|
|
10
|
+
"forEach",
|
|
11
|
+
"includes",
|
|
12
|
+
"indexOf",
|
|
13
|
+
"at",
|
|
14
|
+
"pop",
|
|
15
|
+
"push",
|
|
16
|
+
"shift",
|
|
17
|
+
"unshift",
|
|
18
|
+
"concat",
|
|
19
|
+
]);
|
|
20
|
+
export const hasArrayLikeSplitUsage = (binding) => {
|
|
21
|
+
// Intentionally conservative: require local array-like evidence to reduce
|
|
22
|
+
// false positives from arbitrary `.split(...)` methods on non-strings.
|
|
23
|
+
return binding.referencePaths.some((referencePath) => {
|
|
24
|
+
const parentPath = referencePath.parentPath;
|
|
25
|
+
if (!parentPath)
|
|
26
|
+
return false;
|
|
27
|
+
if (parentPath.isMemberExpression() ||
|
|
28
|
+
parentPath.isOptionalMemberExpression()) {
|
|
29
|
+
const member = parentPath.node;
|
|
30
|
+
if (member.object !== referencePath.node)
|
|
31
|
+
return false;
|
|
32
|
+
if (member.computed) {
|
|
33
|
+
if (member.property.type === "NumericLiteral")
|
|
34
|
+
return true;
|
|
35
|
+
if (member.property.type !== "StringLiteral")
|
|
36
|
+
return false;
|
|
37
|
+
return /^\d+$/u.test(member.property.value);
|
|
38
|
+
}
|
|
39
|
+
if (member.property.type !== "Identifier")
|
|
40
|
+
return false;
|
|
41
|
+
return ARRAY_USAGE_MEMBER_NAMES.has(member.property.name);
|
|
42
|
+
}
|
|
43
|
+
if (parentPath.isVariableDeclarator()) {
|
|
44
|
+
const { id, init } = parentPath.node;
|
|
45
|
+
if (init !== referencePath.node)
|
|
46
|
+
return false;
|
|
47
|
+
if (id.type === "ArrayPattern" || id.type === "ObjectPattern") {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (parentPath.isAssignmentExpression()) {
|
|
52
|
+
const { left, right, operator } = parentPath.node;
|
|
53
|
+
if (operator !== "=" || right !== referencePath.node)
|
|
54
|
+
return false;
|
|
55
|
+
if (left.type === "ArrayPattern" || left.type === "ObjectPattern") {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (parentPath.isForOfStatement()) {
|
|
60
|
+
return parentPath.node.right === referencePath.node;
|
|
61
|
+
}
|
|
62
|
+
if (parentPath.isSpreadElement()) {
|
|
63
|
+
return parentPath.node.argument === referencePath.node;
|
|
64
|
+
}
|
|
65
|
+
if (parentPath.isArrayExpression()) {
|
|
66
|
+
return parentPath.node.elements.includes(referencePath.node);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const isTypeOnlyImportBinding = (scope) => {
|
|
2
|
+
const binding = scope.getBinding("Boolean");
|
|
3
|
+
if (!binding)
|
|
4
|
+
return false;
|
|
5
|
+
const parentImport = binding.path.parentPath?.isImportDeclaration()
|
|
6
|
+
? binding.path.parentPath
|
|
7
|
+
: binding.path.findParent((path) => path.isImportDeclaration());
|
|
8
|
+
if (!parentImport?.isImportDeclaration())
|
|
9
|
+
return false;
|
|
10
|
+
if (parentImport.node.importKind === "type")
|
|
11
|
+
return true;
|
|
12
|
+
if (!binding.path.isImportSpecifier())
|
|
13
|
+
return false;
|
|
14
|
+
return binding.path.node.importKind === "type";
|
|
15
|
+
};
|
|
16
|
+
const isDeclareValueBinding = (scope) => {
|
|
17
|
+
const binding = scope.getBinding("Boolean");
|
|
18
|
+
if (!binding)
|
|
19
|
+
return false;
|
|
20
|
+
if (binding.path.isFunctionDeclaration() &&
|
|
21
|
+
binding.path.node.declare === true) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (binding.path.isClassDeclaration() && binding.path.node.declare === true) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const declarationPath = binding.path.findParent((path) => path.isVariableDeclaration());
|
|
28
|
+
if (!declarationPath?.isVariableDeclaration())
|
|
29
|
+
return false;
|
|
30
|
+
return declarationPath.node.declare === true;
|
|
31
|
+
};
|
|
32
|
+
export const isRuntimeBooleanShadowed = (scope) => {
|
|
33
|
+
const binding = scope.getBinding("Boolean");
|
|
34
|
+
if (!binding)
|
|
35
|
+
return false;
|
|
36
|
+
if (isTypeOnlyImportBinding(scope))
|
|
37
|
+
return false;
|
|
38
|
+
if (isDeclareValueBinding(scope))
|
|
39
|
+
return false;
|
|
40
|
+
if (binding.path.isTSTypeAliasDeclaration() ||
|
|
41
|
+
binding.path.isTSInterfaceDeclaration() ||
|
|
42
|
+
binding.path.isTSTypeParameter()) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"recommended": true,
|
|
3
|
+
"evaluations": {
|
|
4
|
+
"claude-code-2.1.10:claude-code-2.1.11": {
|
|
5
|
+
"diffSizePercent": 99.99811516350957,
|
|
6
|
+
"evaluatedAt": "2026-02-06T14:53:55.285Z",
|
|
7
|
+
"changedLines": 1197,
|
|
8
|
+
"durationSeconds": 209.837459189,
|
|
9
|
+
"stableNames": 1359
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"notes": "Extends manual split helper renames to include direct split results. v2 helper modules are intentionally self-contained to match the project convention for versioned transforms."
|
|
13
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { collectExportedNames } from "../../core/collect-exported-names.js";
|
|
3
|
+
import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
|
|
4
|
+
import { getFilesToProcess } from "../../core/types.js";
|
|
5
|
+
import { hasDynamicNameLookup } from "../has-dynamic-name-lookup.js";
|
|
6
|
+
import { detectSplitResultCandidate } from "./detect-split-result-candidate.js";
|
|
7
|
+
import { getSplitHelperInfo } from "./get-split-helper-info.js";
|
|
8
|
+
import { hasArrayLikeSplitUsage } from "./has-array-like-split-usage.js";
|
|
9
|
+
import { isRuntimeBooleanShadowed } from "./is-runtime-boolean-shadowed.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
12
|
+
const traverse = require("@babel/traverse").default;
|
|
13
|
+
const collectSplitHelperRenames = (path, group) => {
|
|
14
|
+
// Intentionally no early-return based on stable-renamed params:
|
|
15
|
+
// parameters can be stable while helper locals are still minified.
|
|
16
|
+
const info = getSplitHelperInfo(path.node);
|
|
17
|
+
if (!info)
|
|
18
|
+
return;
|
|
19
|
+
const scope = path.scope;
|
|
20
|
+
group.add({
|
|
21
|
+
scope,
|
|
22
|
+
currentName: info.inputName,
|
|
23
|
+
baseName: "input",
|
|
24
|
+
});
|
|
25
|
+
group.add({
|
|
26
|
+
scope,
|
|
27
|
+
currentName: info.delimiterName,
|
|
28
|
+
baseName: "delimiter",
|
|
29
|
+
});
|
|
30
|
+
group.add({
|
|
31
|
+
scope,
|
|
32
|
+
currentName: info.partsName,
|
|
33
|
+
baseName: "parts",
|
|
34
|
+
});
|
|
35
|
+
group.add({
|
|
36
|
+
scope,
|
|
37
|
+
currentName: info.startIndexName,
|
|
38
|
+
baseName: "startIndex",
|
|
39
|
+
});
|
|
40
|
+
group.add({
|
|
41
|
+
scope,
|
|
42
|
+
currentName: info.delimiterIndexName,
|
|
43
|
+
baseName: "delimiterIndex",
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const collectSplitResultRenames = (path, group, exportedNames) => {
|
|
47
|
+
const candidate = detectSplitResultCandidate(path.node);
|
|
48
|
+
if (!candidate)
|
|
49
|
+
return;
|
|
50
|
+
const { currentName } = candidate;
|
|
51
|
+
if (isStableRenamed(currentName))
|
|
52
|
+
return;
|
|
53
|
+
const binding = path.scope.getBinding(currentName);
|
|
54
|
+
if (!binding)
|
|
55
|
+
return;
|
|
56
|
+
if (!binding.constant)
|
|
57
|
+
return;
|
|
58
|
+
if (!hasArrayLikeSplitUsage(binding))
|
|
59
|
+
return;
|
|
60
|
+
if (candidate.requiresGlobalBoolean &&
|
|
61
|
+
// Candidate detection currently only marks `.filter(Boolean)` in the
|
|
62
|
+
// initializer expression, so declaration scope matches reference scope.
|
|
63
|
+
isRuntimeBooleanShadowed(binding.path.scope)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (binding.scope.block.type === "Program" &&
|
|
67
|
+
exportedNames.has(currentName)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
group.add({
|
|
71
|
+
scope: binding.scope,
|
|
72
|
+
currentName,
|
|
73
|
+
baseName: "parts",
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
export const renameStringSplitVariablesV2Transform = {
|
|
77
|
+
id: "rename-string-split-variables-v2",
|
|
78
|
+
description: "Renames variables holding string split results to stable $parts names (covers manual helpers and direct split results)",
|
|
79
|
+
scope: "file",
|
|
80
|
+
parallelizable: true,
|
|
81
|
+
transform(context) {
|
|
82
|
+
let nodesVisited = 0;
|
|
83
|
+
let transformationsApplied = 0;
|
|
84
|
+
for (const fileInfo of getFilesToProcess(context)) {
|
|
85
|
+
const hasDynamicLookup = hasDynamicNameLookup(fileInfo.ast);
|
|
86
|
+
const group = new RenameGroup();
|
|
87
|
+
const exportedNames = collectExportedNames(fileInfo.ast.program);
|
|
88
|
+
traverse(fileInfo.ast, {
|
|
89
|
+
FunctionDeclaration(path) {
|
|
90
|
+
nodesVisited++;
|
|
91
|
+
collectSplitHelperRenames(path, group);
|
|
92
|
+
},
|
|
93
|
+
FunctionExpression(path) {
|
|
94
|
+
nodesVisited++;
|
|
95
|
+
collectSplitHelperRenames(path, group);
|
|
96
|
+
},
|
|
97
|
+
ArrowFunctionExpression(path) {
|
|
98
|
+
nodesVisited++;
|
|
99
|
+
collectSplitHelperRenames(path, group);
|
|
100
|
+
},
|
|
101
|
+
VariableDeclarator(path) {
|
|
102
|
+
nodesVisited++;
|
|
103
|
+
// Dynamic name lookup can observe program-scope bindings by name, so
|
|
104
|
+
// we skip direct split-result renames in those files. Helper renames
|
|
105
|
+
// stay enabled because `getSplitHelperInfo` only matches strict
|
|
106
|
+
// local helper structure and renames function-scoped locals.
|
|
107
|
+
if (hasDynamicLookup)
|
|
108
|
+
return;
|
|
109
|
+
collectSplitResultRenames(path, group, exportedNames);
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
transformationsApplied += group.apply();
|
|
113
|
+
}
|
|
114
|
+
return Promise.resolve({ nodesVisited, transformationsApplied });
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Statement } from "@babel/types";
|
|
2
|
+
export type SplitHelperDeclarations = {
|
|
3
|
+
partsName: string;
|
|
4
|
+
startIndexName: string;
|
|
5
|
+
delimiterIndexName: string;
|
|
6
|
+
};
|
|
7
|
+
type SplitHelperDeclarationsMatch = SplitHelperDeclarations & {
|
|
8
|
+
nextStatementIndex: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const getSplitHelperDeclarationsFromStatements: (statements: Statement[], inputName: string, delimiterName: string) => SplitHelperDeclarationsMatch | undefined;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { isIdentifierNode, isMemberCall, isNumericLiteralNode, } from "./split-helper-guards.js";
|
|
2
|
+
const isDirectivePrologueStatement = (statement) => {
|
|
3
|
+
// In JavaScript, any leading sequence of `"..."`; expression statements is the
|
|
4
|
+
// "directive prologue" (most commonly `"use strict";`). Babel sometimes also
|
|
5
|
+
// marks these with `statement.directive`, but we intentionally only rely on
|
|
6
|
+
// the lexical shape so we still match after AST rewrites.
|
|
7
|
+
if (statement.type !== "ExpressionStatement")
|
|
8
|
+
return false;
|
|
9
|
+
return statement.expression.type === "StringLiteral";
|
|
10
|
+
};
|
|
11
|
+
const getVariableDeclarators = (statement) => {
|
|
12
|
+
if (statement.type !== "VariableDeclaration")
|
|
13
|
+
return undefined;
|
|
14
|
+
if (statement.declarations.length === 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
return statement.declarations;
|
|
17
|
+
};
|
|
18
|
+
export const getSplitHelperDeclarationsFromStatements = (statements, inputName, delimiterName) => {
|
|
19
|
+
const declarators = [];
|
|
20
|
+
let statementIndex = 0;
|
|
21
|
+
while (statementIndex < statements.length) {
|
|
22
|
+
const statement = statements[statementIndex];
|
|
23
|
+
if (!statement)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (!isDirectivePrologueStatement(statement))
|
|
26
|
+
break;
|
|
27
|
+
statementIndex++;
|
|
28
|
+
}
|
|
29
|
+
if (statementIndex >= statements.length)
|
|
30
|
+
return undefined;
|
|
31
|
+
let declarationStatementsConsumed = 0;
|
|
32
|
+
while (declarators.length < 3) {
|
|
33
|
+
if (statementIndex >= statements.length)
|
|
34
|
+
return undefined;
|
|
35
|
+
const statement = statements[statementIndex];
|
|
36
|
+
if (!statement)
|
|
37
|
+
return undefined;
|
|
38
|
+
const statementDeclarators = getVariableDeclarators(statement);
|
|
39
|
+
if (!statementDeclarators)
|
|
40
|
+
return undefined;
|
|
41
|
+
declarationStatementsConsumed++;
|
|
42
|
+
if (declarationStatementsConsumed > 3)
|
|
43
|
+
return undefined;
|
|
44
|
+
const totalDeclarators = declarators.length + statementDeclarators.length;
|
|
45
|
+
if (totalDeclarators > 3)
|
|
46
|
+
return undefined;
|
|
47
|
+
declarators.push(...statementDeclarators);
|
|
48
|
+
statementIndex++;
|
|
49
|
+
}
|
|
50
|
+
const identifierDeclarators = declarators.filter((declarator) => declarator.id.type === "Identifier");
|
|
51
|
+
if (identifierDeclarators.length !== declarators.length)
|
|
52
|
+
return undefined;
|
|
53
|
+
const partsDeclarator = identifierDeclarators.find((declarator) => {
|
|
54
|
+
if (declarator.init?.type !== "ArrayExpression")
|
|
55
|
+
return false;
|
|
56
|
+
return declarator.init.elements.length === 0;
|
|
57
|
+
});
|
|
58
|
+
const startDeclarator = identifierDeclarators.find((declarator) => isNumericLiteralNode(declarator.init ?? undefined, 0));
|
|
59
|
+
const indexDeclarator = identifierDeclarators.find((declarator) => {
|
|
60
|
+
if (declarator.init?.type !== "CallExpression")
|
|
61
|
+
return false;
|
|
62
|
+
if (!isMemberCall(declarator.init, inputName, "indexOf"))
|
|
63
|
+
return false;
|
|
64
|
+
if (!isIdentifierNode(declarator.init.arguments[0], delimiterName))
|
|
65
|
+
return false;
|
|
66
|
+
if (declarator.init.arguments.length === 1)
|
|
67
|
+
return true;
|
|
68
|
+
if (declarator.init.arguments.length !== 2)
|
|
69
|
+
return false;
|
|
70
|
+
return isNumericLiteralNode(declarator.init.arguments[1], 0);
|
|
71
|
+
});
|
|
72
|
+
if (!partsDeclarator || !startDeclarator || !indexDeclarator)
|
|
73
|
+
return undefined;
|
|
74
|
+
return {
|
|
75
|
+
partsName: partsDeclarator.id.name,
|
|
76
|
+
startIndexName: startDeclarator.id.name,
|
|
77
|
+
delimiterIndexName: indexDeclarator.id.name,
|
|
78
|
+
nextStatementIndex: statementIndex,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BinaryExpression, CallExpression, Identifier, Node, NumericLiteral, Statement } from "@babel/types";
|
|
2
|
+
export declare const isIdentifierNode: (node: Node | undefined, name?: string) => node is Identifier;
|
|
3
|
+
export declare const isNumericLiteralNode: (node: Node | undefined, value?: number) => node is NumericLiteral;
|
|
4
|
+
export declare const getBlockStatements: (statement: Statement) => Statement[] | undefined;
|
|
5
|
+
export declare const isMemberCall: (expression: CallExpression, objectName: string, propertyName: string) => boolean;
|
|
6
|
+
export declare const isPushSliceStatement: (statement: Statement, partsName: string, inputName: string, startName: string, endName?: string) => boolean;
|
|
7
|
+
export declare const isAssignmentStatement: (statement: Statement, targetName: string, expectedExpression: (expression: Node) => boolean) => boolean;
|
|
8
|
+
export declare const isBinaryExpression: (expression: Node, operator: BinaryExpression["operator"], leftName: string, rightValue: number) => boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export const isIdentifierNode = (node, name) => {
|
|
2
|
+
if (node?.type !== "Identifier")
|
|
3
|
+
return false;
|
|
4
|
+
return name ? node.name === name : true;
|
|
5
|
+
};
|
|
6
|
+
export const isNumericLiteralNode = (node, value) => {
|
|
7
|
+
if (node?.type !== "NumericLiteral")
|
|
8
|
+
return false;
|
|
9
|
+
return value === undefined ? true : node.value === value;
|
|
10
|
+
};
|
|
11
|
+
export const getBlockStatements = (statement) => {
|
|
12
|
+
if (statement.type !== "BlockStatement")
|
|
13
|
+
return undefined;
|
|
14
|
+
return statement.body;
|
|
15
|
+
};
|
|
16
|
+
export const isMemberCall = (expression, objectName, propertyName) => {
|
|
17
|
+
const callee = expression.callee;
|
|
18
|
+
if (callee.type !== "MemberExpression")
|
|
19
|
+
return false;
|
|
20
|
+
if (callee.computed)
|
|
21
|
+
return false;
|
|
22
|
+
if (!isIdentifierNode(callee.object, objectName))
|
|
23
|
+
return false;
|
|
24
|
+
return isIdentifierNode(callee.property, propertyName);
|
|
25
|
+
};
|
|
26
|
+
const isSliceCallWithArguments = (expression, inputName, startName, endName) => {
|
|
27
|
+
if (!isMemberCall(expression, inputName, "slice"))
|
|
28
|
+
return false;
|
|
29
|
+
if (!isIdentifierNode(expression.arguments[0], startName))
|
|
30
|
+
return false;
|
|
31
|
+
if (endName === undefined) {
|
|
32
|
+
return expression.arguments.length === 1;
|
|
33
|
+
}
|
|
34
|
+
if (!isIdentifierNode(expression.arguments[1], endName))
|
|
35
|
+
return false;
|
|
36
|
+
return expression.arguments.length === 2;
|
|
37
|
+
};
|
|
38
|
+
export const isPushSliceStatement = (statement, partsName, inputName, startName, endName) => {
|
|
39
|
+
if (statement.type !== "ExpressionStatement")
|
|
40
|
+
return false;
|
|
41
|
+
const expression = statement.expression;
|
|
42
|
+
if (expression.type !== "CallExpression")
|
|
43
|
+
return false;
|
|
44
|
+
if (!isMemberCall(expression, partsName, "push"))
|
|
45
|
+
return false;
|
|
46
|
+
if (expression.arguments.length !== 1)
|
|
47
|
+
return false;
|
|
48
|
+
const argument0 = expression.arguments[0];
|
|
49
|
+
if (argument0?.type !== "CallExpression")
|
|
50
|
+
return false;
|
|
51
|
+
return isSliceCallWithArguments(argument0, inputName, startName, endName);
|
|
52
|
+
};
|
|
53
|
+
export const isAssignmentStatement = (statement, targetName, expectedExpression) => {
|
|
54
|
+
if (statement.type !== "ExpressionStatement")
|
|
55
|
+
return false;
|
|
56
|
+
const expression = statement.expression;
|
|
57
|
+
if (expression.type !== "AssignmentExpression")
|
|
58
|
+
return false;
|
|
59
|
+
if (expression.operator !== "=")
|
|
60
|
+
return false;
|
|
61
|
+
if (!isIdentifierNode(expression.left, targetName))
|
|
62
|
+
return false;
|
|
63
|
+
return expectedExpression(expression.right);
|
|
64
|
+
};
|
|
65
|
+
export const isBinaryExpression = (expression, operator, leftName, rightValue) => {
|
|
66
|
+
if (expression.type !== "BinaryExpression")
|
|
67
|
+
return false;
|
|
68
|
+
if (expression.operator !== operator)
|
|
69
|
+
return false;
|
|
70
|
+
if (!isIdentifierNode(expression.left, leftName))
|
|
71
|
+
return false;
|
|
72
|
+
return isNumericLiteralNode(expression.right, rightValue);
|
|
73
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Statement } from "@babel/types";
|
|
2
|
+
import type { SplitHelperDeclarations } from "./split-helper-declarations.js";
|
|
3
|
+
export declare const isSplitHelperLoop: (statement: Statement | undefined, declarations: SplitHelperDeclarations, inputName: string, delimiterName: string) => boolean;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { isAssignmentStatement, isBinaryExpression, isIdentifierNode, isMemberCall, isNumericLiteralNode, isPushSliceStatement, } from "./split-helper-guards.js";
|
|
2
|
+
const isNegativeOneLiteral = (expression) => {
|
|
3
|
+
if (expression.type === "NumericLiteral")
|
|
4
|
+
return expression.value === -1;
|
|
5
|
+
if (expression.type !== "UnaryExpression")
|
|
6
|
+
return false;
|
|
7
|
+
if (expression.operator !== "-")
|
|
8
|
+
return false;
|
|
9
|
+
if (expression.argument.type !== "NumericLiteral")
|
|
10
|
+
return false;
|
|
11
|
+
return expression.argument.value === 1;
|
|
12
|
+
};
|
|
13
|
+
const isNotMinusOneBinaryTest = (expression, name) => {
|
|
14
|
+
if (expression.type !== "BinaryExpression")
|
|
15
|
+
return false;
|
|
16
|
+
if (expression.operator !== "!==" && expression.operator !== "!=")
|
|
17
|
+
return false;
|
|
18
|
+
if (!isIdentifierNode(expression.left, name))
|
|
19
|
+
return false;
|
|
20
|
+
return isNegativeOneLiteral(expression.right);
|
|
21
|
+
};
|
|
22
|
+
const isFoundBinaryTest = (expression, name) => {
|
|
23
|
+
if (expression.type !== "BinaryExpression")
|
|
24
|
+
return false;
|
|
25
|
+
if (expression.operator !== ">")
|
|
26
|
+
return false;
|
|
27
|
+
if (!isIdentifierNode(expression.left, name))
|
|
28
|
+
return false;
|
|
29
|
+
return isNegativeOneLiteral(expression.right);
|
|
30
|
+
};
|
|
31
|
+
const isBitwiseNotTest = (expression, name) => {
|
|
32
|
+
if (expression.type !== "UnaryExpression")
|
|
33
|
+
return false;
|
|
34
|
+
if (expression.operator !== "~")
|
|
35
|
+
return false;
|
|
36
|
+
return isIdentifierNode(expression.argument, name);
|
|
37
|
+
};
|
|
38
|
+
export const isSplitHelperLoop = (statement, declarations, inputName, delimiterName) => {
|
|
39
|
+
if (statement?.type !== "WhileStatement")
|
|
40
|
+
return false;
|
|
41
|
+
const isNonNegativeTest = (expression) => {
|
|
42
|
+
if (isBinaryExpression(expression, ">=", declarations.delimiterIndexName, 0)) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (expression.type !== "BinaryExpression")
|
|
46
|
+
return false;
|
|
47
|
+
if (expression.operator !== "<=")
|
|
48
|
+
return false;
|
|
49
|
+
if (!isNumericLiteralNode(expression.left, 0))
|
|
50
|
+
return false;
|
|
51
|
+
return isIdentifierNode(expression.right, declarations.delimiterIndexName);
|
|
52
|
+
};
|
|
53
|
+
if (!isNonNegativeTest(statement.test) &&
|
|
54
|
+
!isNotMinusOneBinaryTest(statement.test, declarations.delimiterIndexName) &&
|
|
55
|
+
!isFoundBinaryTest(statement.test, declarations.delimiterIndexName) &&
|
|
56
|
+
!isBitwiseNotTest(statement.test, declarations.delimiterIndexName)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (statement.body.type !== "BlockStatement")
|
|
60
|
+
return false;
|
|
61
|
+
if (statement.body.body.length !== 3)
|
|
62
|
+
return false;
|
|
63
|
+
const [pushStatement, advanceStatement, updateStatement] = statement.body.body;
|
|
64
|
+
if (!pushStatement || !advanceStatement || !updateStatement)
|
|
65
|
+
return false;
|
|
66
|
+
if (!isPushSliceStatement(pushStatement, declarations.partsName, inputName, declarations.startIndexName, declarations.delimiterIndexName))
|
|
67
|
+
return false;
|
|
68
|
+
const isAdvanceExpression = (expression) => {
|
|
69
|
+
if (isBinaryExpression(expression, "+", declarations.delimiterIndexName, 1)) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
if (expression.type === "BinaryExpression" && expression.operator === "+") {
|
|
73
|
+
if (isNumericLiteralNode(expression.left, 1) &&
|
|
74
|
+
isIdentifierNode(expression.right, declarations.delimiterIndexName)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
const left = expression.left;
|
|
78
|
+
const right = expression.right;
|
|
79
|
+
if (left.type === "MemberExpression" &&
|
|
80
|
+
!left.computed &&
|
|
81
|
+
isIdentifierNode(left.object, delimiterName) &&
|
|
82
|
+
isIdentifierNode(left.property, "length") &&
|
|
83
|
+
isIdentifierNode(right, declarations.delimiterIndexName)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (expression.type !== "BinaryExpression")
|
|
88
|
+
return false;
|
|
89
|
+
if (expression.operator !== "+")
|
|
90
|
+
return false;
|
|
91
|
+
if (!isIdentifierNode(expression.left, declarations.delimiterIndexName))
|
|
92
|
+
return false;
|
|
93
|
+
const right = expression.right;
|
|
94
|
+
if (right.type !== "MemberExpression")
|
|
95
|
+
return false;
|
|
96
|
+
if (right.computed)
|
|
97
|
+
return false;
|
|
98
|
+
if (!isIdentifierNode(right.object, delimiterName))
|
|
99
|
+
return false;
|
|
100
|
+
return isIdentifierNode(right.property, "length");
|
|
101
|
+
};
|
|
102
|
+
if (!isAssignmentStatement(advanceStatement, declarations.startIndexName, isAdvanceExpression))
|
|
103
|
+
return false;
|
|
104
|
+
const isUpdateExpression = (expression) => {
|
|
105
|
+
if (expression.type !== "CallExpression")
|
|
106
|
+
return false;
|
|
107
|
+
if (!isMemberCall(expression, inputName, "indexOf"))
|
|
108
|
+
return false;
|
|
109
|
+
if (expression.arguments.length !== 2)
|
|
110
|
+
return false;
|
|
111
|
+
if (!isIdentifierNode(expression.arguments[0], delimiterName))
|
|
112
|
+
return false;
|
|
113
|
+
return isIdentifierNode(expression.arguments[1], declarations.startIndexName);
|
|
114
|
+
};
|
|
115
|
+
return isAssignmentStatement(updateStatement, declarations.delimiterIndexName, isUpdateExpression);
|
|
116
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isIdentifierNode } from "./split-helper-guards.js";
|
|
2
|
+
import { isAtEndTest, isShouldPushTailTest, } from "./split-helper-tail-tests.js";
|
|
3
|
+
const isPushSliceCallExpression = (node, declarations, inputName) => {
|
|
4
|
+
if (node.type !== "CallExpression")
|
|
5
|
+
return false;
|
|
6
|
+
const callee = node.callee;
|
|
7
|
+
if (callee.type !== "MemberExpression")
|
|
8
|
+
return false;
|
|
9
|
+
if (callee.computed)
|
|
10
|
+
return false;
|
|
11
|
+
if (!isIdentifierNode(callee.object, declarations.partsName))
|
|
12
|
+
return false;
|
|
13
|
+
if (!isIdentifierNode(callee.property, "push"))
|
|
14
|
+
return false;
|
|
15
|
+
if (node.arguments.length !== 1)
|
|
16
|
+
return false;
|
|
17
|
+
const argument0 = node.arguments[0];
|
|
18
|
+
if (argument0?.type !== "CallExpression")
|
|
19
|
+
return false;
|
|
20
|
+
const sliceCallee = argument0.callee;
|
|
21
|
+
if (sliceCallee.type !== "MemberExpression")
|
|
22
|
+
return false;
|
|
23
|
+
if (sliceCallee.computed)
|
|
24
|
+
return false;
|
|
25
|
+
if (!isIdentifierNode(sliceCallee.object, inputName))
|
|
26
|
+
return false;
|
|
27
|
+
if (!isIdentifierNode(sliceCallee.property, "slice"))
|
|
28
|
+
return false;
|
|
29
|
+
if (argument0.arguments.length !== 1)
|
|
30
|
+
return false;
|
|
31
|
+
return isIdentifierNode(argument0.arguments[0], declarations.startIndexName);
|
|
32
|
+
};
|
|
33
|
+
const isPushSequenceExpression = (node, declarations, inputName) => {
|
|
34
|
+
if (node.type !== "SequenceExpression")
|
|
35
|
+
return false;
|
|
36
|
+
if (node.expressions.length !== 2)
|
|
37
|
+
return false;
|
|
38
|
+
const [first, second] = node.expressions;
|
|
39
|
+
if (!first || !second)
|
|
40
|
+
return false;
|
|
41
|
+
if (!isPushSliceCallExpression(first, declarations, inputName))
|
|
42
|
+
return false;
|
|
43
|
+
return isIdentifierNode(second, declarations.partsName);
|
|
44
|
+
};
|
|
45
|
+
export const isReturnWithOptionalTailPush = (node, declarations, inputName) => {
|
|
46
|
+
if (node.type === "ConditionalExpression") {
|
|
47
|
+
const testIsShouldPush = isShouldPushTailTest(node.test, declarations, inputName);
|
|
48
|
+
const testIsAtEnd = isAtEndTest(node.test, declarations, inputName);
|
|
49
|
+
if (!testIsShouldPush && !testIsAtEnd)
|
|
50
|
+
return false;
|
|
51
|
+
const consequent = node.consequent;
|
|
52
|
+
const alternate = node.alternate;
|
|
53
|
+
const consequentIsParts = isIdentifierNode(consequent, declarations.partsName);
|
|
54
|
+
const alternateIsParts = isIdentifierNode(alternate, declarations.partsName);
|
|
55
|
+
const consequentIsPush = isPushSequenceExpression(consequent, declarations, inputName);
|
|
56
|
+
const alternateIsPush = isPushSequenceExpression(alternate, declarations, inputName);
|
|
57
|
+
if (testIsShouldPush) {
|
|
58
|
+
if (!consequentIsPush)
|
|
59
|
+
return false;
|
|
60
|
+
return alternateIsParts;
|
|
61
|
+
}
|
|
62
|
+
if (!testIsAtEnd)
|
|
63
|
+
return false;
|
|
64
|
+
if (!alternateIsPush)
|
|
65
|
+
return false;
|
|
66
|
+
return consequentIsParts;
|
|
67
|
+
}
|
|
68
|
+
if (node.type !== "SequenceExpression")
|
|
69
|
+
return false;
|
|
70
|
+
if (node.expressions.length !== 2)
|
|
71
|
+
return false;
|
|
72
|
+
const [first, second] = node.expressions;
|
|
73
|
+
if (!first || !second)
|
|
74
|
+
return false;
|
|
75
|
+
if (!isIdentifierNode(second, declarations.partsName))
|
|
76
|
+
return false;
|
|
77
|
+
if (isPushSliceCallExpression(first, declarations, inputName))
|
|
78
|
+
return true;
|
|
79
|
+
if (first.type === "LogicalExpression" &&
|
|
80
|
+
first.operator === "&&" &&
|
|
81
|
+
isShouldPushTailTest(first.left, declarations, inputName) &&
|
|
82
|
+
isPushSliceCallExpression(first.right, declarations, inputName)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Node } from "@babel/types";
|
|
2
|
+
import type { SplitHelperDeclarations } from "./split-helper-declarations.js";
|
|
3
|
+
export declare const isShouldPushTailTest: (node: Node, declarations: SplitHelperDeclarations, inputName: string) => boolean;
|
|
4
|
+
export declare const isAtEndTest: (node: Node, declarations: SplitHelperDeclarations, inputName: string) => boolean;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isIdentifierNode } from "./split-helper-guards.js";
|
|
2
|
+
const isInputLengthMemberExpression = (node, inputName) => {
|
|
3
|
+
if (node.type !== "MemberExpression")
|
|
4
|
+
return false;
|
|
5
|
+
if (node.computed)
|
|
6
|
+
return false;
|
|
7
|
+
if (!isIdentifierNode(node.object, inputName))
|
|
8
|
+
return false;
|
|
9
|
+
return isIdentifierNode(node.property, "length");
|
|
10
|
+
};
|
|
11
|
+
export const isShouldPushTailTest = (node, declarations, inputName) => {
|
|
12
|
+
if (node.type !== "BinaryExpression")
|
|
13
|
+
return false;
|
|
14
|
+
const operator = node.operator;
|
|
15
|
+
if ((operator === "!==" || operator === "!=" || operator === "<") &&
|
|
16
|
+
isIdentifierNode(node.left, declarations.startIndexName) &&
|
|
17
|
+
isInputLengthMemberExpression(node.right, inputName)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if ((operator === "!==" || operator === "!=" || operator === ">") &&
|
|
21
|
+
isInputLengthMemberExpression(node.left, inputName) &&
|
|
22
|
+
isIdentifierNode(node.right, declarations.startIndexName)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
};
|
|
27
|
+
export const isAtEndTest = (node, declarations, inputName) => {
|
|
28
|
+
if (node.type !== "BinaryExpression")
|
|
29
|
+
return false;
|
|
30
|
+
const operator = node.operator;
|
|
31
|
+
if (operator !== "===" && operator !== "==")
|
|
32
|
+
return false;
|
|
33
|
+
if (isIdentifierNode(node.left, declarations.startIndexName) &&
|
|
34
|
+
isInputLengthMemberExpression(node.right, inputName)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return (isInputLengthMemberExpression(node.left, inputName) &&
|
|
38
|
+
isIdentifierNode(node.right, declarations.startIndexName));
|
|
39
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getBlockStatements, isIdentifierNode, isPushSliceStatement, } from "./split-helper-guards.js";
|
|
2
|
+
import { isReturnWithOptionalTailPush } from "./split-helper-tail-expressions.js";
|
|
3
|
+
import { isShouldPushTailTest } from "./split-helper-tail-tests.js";
|
|
4
|
+
const isReturnPartsStatement = (statement, declarations) => {
|
|
5
|
+
if (statement?.type !== "ReturnStatement")
|
|
6
|
+
return false;
|
|
7
|
+
return isIdentifierNode(statement.argument ?? undefined, declarations.partsName);
|
|
8
|
+
};
|
|
9
|
+
export const isSplitHelperTail = (tailStatements, declarations, inputName) => {
|
|
10
|
+
if (tailStatements.length === 2) {
|
|
11
|
+
const [firstTailStatement, secondTailStatement] = tailStatements;
|
|
12
|
+
if (!firstTailStatement || !secondTailStatement)
|
|
13
|
+
return false;
|
|
14
|
+
if (firstTailStatement.type === "IfStatement") {
|
|
15
|
+
const ifStatement = firstTailStatement;
|
|
16
|
+
const returnStatement = secondTailStatement;
|
|
17
|
+
if (ifStatement.alternate)
|
|
18
|
+
return false;
|
|
19
|
+
if (!isShouldPushTailTest(ifStatement.test, declarations, inputName))
|
|
20
|
+
return false;
|
|
21
|
+
const ifStatements = getBlockStatements(ifStatement.consequent) ?? [
|
|
22
|
+
ifStatement.consequent,
|
|
23
|
+
];
|
|
24
|
+
if (ifStatements.length !== 1)
|
|
25
|
+
return false;
|
|
26
|
+
const ifConsequent = ifStatements[0];
|
|
27
|
+
if (!ifConsequent)
|
|
28
|
+
return false;
|
|
29
|
+
if (!isPushSliceStatement(ifConsequent, declarations.partsName, inputName, declarations.startIndexName))
|
|
30
|
+
return false;
|
|
31
|
+
return isReturnPartsStatement(returnStatement, declarations);
|
|
32
|
+
}
|
|
33
|
+
if (isPushSliceStatement(firstTailStatement, declarations.partsName, inputName, declarations.startIndexName)) {
|
|
34
|
+
return isReturnPartsStatement(secondTailStatement, declarations);
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (tailStatements.length !== 1)
|
|
39
|
+
return false;
|
|
40
|
+
const tailStatement = tailStatements[0];
|
|
41
|
+
if (!tailStatement)
|
|
42
|
+
return false;
|
|
43
|
+
if (tailStatement.type !== "ReturnStatement")
|
|
44
|
+
return false;
|
|
45
|
+
const argument = tailStatement.argument;
|
|
46
|
+
if (!argument)
|
|
47
|
+
return false;
|
|
48
|
+
return isReturnWithOptionalTailPush(argument, declarations, inputName);
|
|
49
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const unwrapTransparentExpression = (expression) => {
|
|
2
|
+
let current = expression;
|
|
3
|
+
while (current.type === "ParenthesizedExpression" ||
|
|
4
|
+
current.type === "TSAsExpression" ||
|
|
5
|
+
current.type === "TSTypeAssertion" ||
|
|
6
|
+
current.type === "TSInstantiationExpression" ||
|
|
7
|
+
current.type === "TSNonNullExpression" ||
|
|
8
|
+
current.type === "TSSatisfiesExpression") {
|
|
9
|
+
current = current.expression;
|
|
10
|
+
}
|
|
11
|
+
return current;
|
|
12
|
+
};
|
|
13
|
+
export const getUnwrappedCallExpression = (expression) => {
|
|
14
|
+
if (!expression)
|
|
15
|
+
return undefined;
|
|
16
|
+
const unwrapped = unwrapTransparentExpression(expression);
|
|
17
|
+
if (unwrapped.type !== "CallExpression" &&
|
|
18
|
+
unwrapped.type !== "OptionalCallExpression") {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return unwrapped;
|
|
22
|
+
};
|
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.88.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",
|