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.
Files changed (32) hide show
  1. package/dist/transforms/_generated/manifest.js +9 -3
  2. package/dist/transforms/_generated/registry.js +2 -0
  3. package/dist/transforms/preset-stats.json +2 -2
  4. package/dist/transforms/recommended-transform-order.d.ts +1 -1
  5. package/dist/transforms/recommended-transform-order.js +1 -1
  6. package/dist/transforms/rename-string-split-variables/manifest.json +3 -2
  7. package/dist/transforms/rename-string-split-variables-v2/detect-split-result-candidate.d.ts +7 -0
  8. package/dist/transforms/rename-string-split-variables-v2/detect-split-result-candidate.js +117 -0
  9. package/dist/transforms/rename-string-split-variables-v2/get-split-helper-info.d.ts +8 -0
  10. package/dist/transforms/rename-string-split-variables-v2/get-split-helper-info.js +40 -0
  11. package/dist/transforms/rename-string-split-variables-v2/has-array-like-split-usage.d.ts +2 -0
  12. package/dist/transforms/rename-string-split-variables-v2/has-array-like-split-usage.js +70 -0
  13. package/dist/transforms/rename-string-split-variables-v2/is-runtime-boolean-shadowed.d.ts +2 -0
  14. package/dist/transforms/rename-string-split-variables-v2/is-runtime-boolean-shadowed.js +46 -0
  15. package/dist/transforms/rename-string-split-variables-v2/manifest.json +13 -0
  16. package/dist/transforms/rename-string-split-variables-v2/rename-string-split-variables-v2-transform.d.ts +2 -0
  17. package/dist/transforms/rename-string-split-variables-v2/rename-string-split-variables-v2-transform.js +116 -0
  18. package/dist/transforms/rename-string-split-variables-v2/split-helper-declarations.d.ts +11 -0
  19. package/dist/transforms/rename-string-split-variables-v2/split-helper-declarations.js +80 -0
  20. package/dist/transforms/rename-string-split-variables-v2/split-helper-guards.d.ts +8 -0
  21. package/dist/transforms/rename-string-split-variables-v2/split-helper-guards.js +73 -0
  22. package/dist/transforms/rename-string-split-variables-v2/split-helper-loop.d.ts +3 -0
  23. package/dist/transforms/rename-string-split-variables-v2/split-helper-loop.js +116 -0
  24. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-expressions.d.ts +3 -0
  25. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-expressions.js +86 -0
  26. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-tests.d.ts +4 -0
  27. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail-tests.js +39 -0
  28. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail.d.ts +3 -0
  29. package/dist/transforms/rename-string-split-variables-v2/split-helper-tail.js +49 -0
  30. package/dist/transforms/rename-string-split-variables-v2/unwrap-transparent-expression.d.ts +2 -0
  31. package/dist/transforms/rename-string-split-variables-v2/unwrap-transparent-expression.js +22 -0
  32. 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: true,
324
- notes: "Measured with baseline none: 0.00%. Added to recommended for readability.",
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,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "recommended": {
3
- "diffSizePercent": 30.431627556309493,
4
- "notes": "Measured with baseline none: 30.43% of original diff."
3
+ "diffSizePercent": 30.510828892164643,
4
+ "notes": "Measured with baseline none: 30.51% of original diff."
5
5
  }
6
6
  }
@@ -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": true,
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": "Measured with baseline none: 0.00%. Added to recommended for readability."
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,2 @@
1
+ import type { Binding } from "@babel/traverse";
2
+ export declare const hasArrayLikeSplitUsage: (binding: Binding) => boolean;
@@ -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,2 @@
1
+ import type { Scope } from "@babel/traverse";
2
+ export declare const isRuntimeBooleanShadowed: (scope: Scope) => boolean;
@@ -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,2 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ export declare const renameStringSplitVariablesV2Transform: Transform;
@@ -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,3 @@
1
+ import type { Node } from "@babel/types";
2
+ import type { SplitHelperDeclarations } from "./split-helper-declarations.js";
3
+ export declare const isReturnWithOptionalTailPush: (node: Node, declarations: SplitHelperDeclarations, inputName: string) => boolean;
@@ -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,3 @@
1
+ import type { Statement } from "@babel/types";
2
+ import type { SplitHelperDeclarations } from "./split-helper-declarations.js";
3
+ export declare const isSplitHelperTail: (tailStatements: Statement[], declarations: SplitHelperDeclarations, inputName: string) => boolean;
@@ -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,2 @@
1
+ import type { CallExpression, Expression, OptionalCallExpression } from "@babel/types";
2
+ export declare const getUnwrappedCallExpression: (expression: Expression | null | undefined) => CallExpression | OptionalCallExpression | undefined;
@@ -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.87.0",
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",