miniread 1.120.0 → 1.120.2

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 (86) hide show
  1. package/dist/transforms/get-url-construction-kind.js +8 -1
  2. package/dist/transforms/preset-stats.json +2 -2
  3. package/dist/transforms/url-usage-heuristics.js +2 -2
  4. package/dist/transforms-by-id/expand-undefined-literals/expand-undefined-literals-transform.js +43 -3
  5. package/dist/transforms-by-id/expand-undefined-literals/manifest.json +3 -3
  6. package/dist/transforms-by-id/remove-redundant-else/does-switch-interrupt-boundary.d.ts +5 -0
  7. package/dist/transforms-by-id/remove-redundant-else/does-switch-interrupt-boundary.js +39 -0
  8. package/dist/transforms-by-id/remove-redundant-else/get-guaranteed-switch-case-index.d.ts +3 -0
  9. package/dist/transforms-by-id/remove-redundant-else/get-guaranteed-switch-case-index.js +58 -0
  10. package/dist/transforms-by-id/remove-redundant-else/get-last-non-empty-statement-index.d.ts +3 -0
  11. package/dist/transforms-by-id/remove-redundant-else/get-last-non-empty-statement-index.js +28 -0
  12. package/dist/transforms-by-id/remove-redundant-else/has-non-interrupting-switch-exit.d.ts +5 -0
  13. package/dist/transforms-by-id/remove-redundant-else/has-non-interrupting-switch-exit.js +44 -0
  14. package/dist/transforms-by-id/remove-redundant-else/is-interrupting-branch.d.ts +3 -0
  15. package/dist/transforms-by-id/remove-redundant-else/is-interrupting-branch.js +124 -0
  16. package/dist/transforms-by-id/remove-redundant-else/manifest.json +3 -3
  17. package/dist/transforms-by-id/remove-redundant-else/remove-redundant-else-transform.js +52 -40
  18. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/deferred-executor-heuristics.js +1 -11
  19. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/manifest.json +3 -3
  20. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/rename-deferred-resolve-parameters-v2-transform.js +16 -23
  21. package/dist/transforms-by-id/rename-error-first-callback-parameters-v2/get-leading-error-first-guard.js +10 -2
  22. package/dist/transforms-by-id/rename-error-first-callback-parameters-v2/manifest.json +5 -5
  23. package/dist/transforms-by-id/rename-error-first-callback-parameters-v2/match-error-first-reject-resolve-pattern.d.ts +1 -1
  24. package/dist/transforms-by-id/rename-error-first-callback-parameters-v2/match-error-first-reject-resolve-pattern.js +17 -10
  25. package/dist/transforms-by-id/rename-error-first-callback-parameters-v2/rename-error-first-callback-parameters-v2-transform.js +7 -5
  26. package/dist/transforms-by-id/rename-event-parameters/event-property-names.d.ts +5 -0
  27. package/dist/transforms-by-id/rename-event-parameters/event-property-names.js +76 -9
  28. package/dist/transforms-by-id/rename-event-parameters/get-target-event-name.js +38 -3
  29. package/dist/transforms-by-id/rename-event-parameters/manifest.json +4 -4
  30. package/dist/transforms-by-id/rename-event-parameters/process-event-handler-function.js +34 -35
  31. package/dist/transforms-by-id/rename-execfile-arguments/detect-execfile-call.d.ts +1 -0
  32. package/dist/transforms-by-id/rename-execfile-arguments/detect-execfile-call.js +28 -62
  33. package/dist/transforms-by-id/rename-execfile-arguments/is-child-process-execfile-binding.d.ts +2 -0
  34. package/dist/transforms-by-id/rename-execfile-arguments/is-child-process-execfile-binding.js +50 -0
  35. package/dist/transforms-by-id/rename-execfile-arguments/manifest.json +3 -3
  36. package/dist/transforms-by-id/rename-execfile-arguments/rename-execfile-arguments-transform.js +45 -27
  37. package/dist/transforms-by-id/rename-file-reader-variables/get-file-reader-event-handler-name.js +5 -5
  38. package/dist/transforms-by-id/rename-file-reader-variables/get-file-reader-handler-function.js +6 -0
  39. package/dist/transforms-by-id/rename-file-reader-variables/get-file-reader-receiver-match.d.ts +10 -0
  40. package/dist/transforms-by-id/rename-file-reader-variables/get-file-reader-receiver-match.js +36 -0
  41. package/dist/transforms-by-id/rename-file-reader-variables/is-file-reader-constructor-expression.d.ts +2 -0
  42. package/dist/transforms-by-id/rename-file-reader-variables/is-file-reader-constructor-expression.js +36 -0
  43. package/dist/transforms-by-id/rename-file-reader-variables/is-known-file-reader-binding-identifier.d.ts +3 -0
  44. package/dist/transforms-by-id/rename-file-reader-variables/is-known-file-reader-binding-identifier.js +9 -0
  45. package/dist/transforms-by-id/rename-file-reader-variables/manifest.json +3 -3
  46. package/dist/transforms-by-id/rename-file-reader-variables/queue-file-reader-event-rename.js +28 -22
  47. package/dist/transforms-by-id/rename-file-reader-variables/rename-file-reader-variables-transform.js +41 -48
  48. package/dist/transforms-by-id/rename-file-reader-variables/track-file-reader-binding-assignment.d.ts +8 -0
  49. package/dist/transforms-by-id/rename-file-reader-variables/track-file-reader-binding-assignment.js +32 -0
  50. package/dist/transforms-by-id/rename-object-entries-parameters/is-object-entries-consumer-call.d.ts +1 -0
  51. package/dist/transforms-by-id/rename-object-entries-parameters/is-object-entries-consumer-call.js +12 -0
  52. package/dist/transforms-by-id/rename-object-entries-parameters/manifest.json +3 -3
  53. package/dist/transforms-by-id/rename-object-entries-parameters/rename-object-entries-parameters-transform.js +53 -26
  54. package/dist/transforms-by-id/rename-object-keys-variables/is-safe-keys-reassignment.d.ts +7 -2
  55. package/dist/transforms-by-id/rename-object-keys-variables/is-safe-keys-reassignment.js +73 -1
  56. package/dist/transforms-by-id/rename-object-keys-variables/manifest.json +3 -3
  57. package/dist/transforms-by-id/rename-object-keys-variables/rename-object-keys-variables-transform.js +59 -42
  58. package/dist/transforms-by-id/rename-object-property-value-variables/get-object-property-value-base-name.js +37 -12
  59. package/dist/transforms-by-id/rename-object-property-value-variables/manifest.json +5 -5
  60. package/dist/transforms-by-id/rename-object-property-value-variables/rename-object-property-value-variables-transform.js +8 -7
  61. package/dist/transforms-by-id/rename-sanitized-string-variables/manifest.json +5 -5
  62. package/dist/transforms-by-id/rename-sanitized-string-variables/rename-sanitized-string-variables-transform.js +52 -34
  63. package/dist/transforms-by-id/rename-timeout-ids/add-rename-candidate-for-timer-assignment.d.ts +12 -0
  64. package/dist/transforms-by-id/rename-timeout-ids/add-rename-candidate-for-timer-assignment.js +59 -0
  65. package/dist/transforms-by-id/rename-timeout-ids/is-global-timer-function-reference.d.ts +4 -0
  66. package/dist/transforms-by-id/rename-timeout-ids/is-global-timer-function-reference.js +64 -0
  67. package/dist/transforms-by-id/rename-timeout-ids/is-timer-function-reference.d.ts +3 -0
  68. package/dist/transforms-by-id/rename-timeout-ids/is-timer-function-reference.js +85 -0
  69. package/dist/transforms-by-id/rename-timeout-ids/manifest.json +4 -4
  70. package/dist/transforms-by-id/rename-timeout-ids/rename-timeout-ids-transform.js +15 -36
  71. package/dist/transforms-by-id/rename-uint8array-concat-variables/find-loop-copy-match.js +25 -1
  72. package/dist/transforms-by-id/rename-uint8array-concat-variables/find-uint8array-concat-match.js +13 -1
  73. package/dist/transforms-by-id/rename-uint8array-concat-variables/find-uint8array-output-rename-matches.d.ts +2 -0
  74. package/dist/transforms-by-id/rename-uint8array-concat-variables/find-uint8array-output-rename-matches.js +44 -0
  75. package/dist/transforms-by-id/rename-uint8array-concat-variables/manifest.json +4 -4
  76. package/dist/transforms-by-id/rename-uint8array-concat-variables/rename-uint8array-concat-variables-transform.js +7 -0
  77. package/dist/transforms-by-id/rename-url-variables/is-allowed-url-construction.d.ts +3 -0
  78. package/dist/transforms-by-id/rename-url-variables/is-allowed-url-construction.js +71 -0
  79. package/dist/transforms-by-id/rename-url-variables/manifest.json +3 -3
  80. package/dist/transforms-by-id/rename-url-variables/rename-url-variables-transform.js +51 -83
  81. package/dist/transforms-by-id/stabilize-deferred-stable-rhs/check-rhs-stability.d.ts +1 -1
  82. package/dist/transforms-by-id/stabilize-deferred-stable-rhs/check-rhs-stability.js +16 -43
  83. package/dist/transforms-by-id/stabilize-deferred-stable-rhs/manifest.json +4 -4
  84. package/dist/transforms-by-id/use-object-property-shorthand/manifest.json +5 -5
  85. package/dist/transforms-by-id/use-object-property-shorthand/use-object-property-shorthand-transform.js +3 -4
  86. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { isAllowedUrlNamespaceBindingInScope } from "./is-allowed-url-namespace-binding-in-scope.js";
2
+ const globalUrlNamespaceObjects = new Set(["globalThis", "window", "self"]);
2
3
  export const getUrlConstructionKind = (scope, node, constructorName) => {
3
4
  if (node?.type !== "NewExpression")
4
5
  return undefined;
@@ -40,7 +41,13 @@ export const getUrlConstructionKind = (scope, node, constructorName) => {
40
41
  return undefined;
41
42
  if (callee.object.type !== "Identifier")
42
43
  return undefined;
43
- if (!isAllowedUrlNamespaceBindingInScope(scope, callee.object.name)) {
44
+ if (isAllowedUrlNamespaceBindingInScope(scope, callee.object.name)) {
45
+ return "member";
46
+ }
47
+ if (!globalUrlNamespaceObjects.has(callee.object.name)) {
48
+ return undefined;
49
+ }
50
+ if (scope.getBinding(callee.object.name)) {
44
51
  return undefined;
45
52
  }
46
53
  return "member";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "recommended": {
3
- "diffSizePercent": 23.705446573685602,
4
- "notes": "Measured with baseline none: 23.71% of original diff."
3
+ "diffSizePercent": 23.180065767093776,
4
+ "notes": "Measured with baseline none: 23.18% of original diff."
5
5
  }
6
6
  }
@@ -38,13 +38,13 @@ export const hasUrlDestructure = (referencePath) => {
38
38
  if (parent.isVariableDeclarator() &&
39
39
  parent.node.init === referencePath.node &&
40
40
  parent.node.id.type === "ObjectPattern") {
41
- return countUrlProperties(parent.node.id) >= 2;
41
+ return countUrlProperties(parent.node.id) >= 1;
42
42
  }
43
43
  if (parent.isAssignmentExpression() &&
44
44
  parent.node.operator === "=" &&
45
45
  parent.node.right === referencePath.node &&
46
46
  parent.node.left.type === "ObjectPattern") {
47
- return countUrlProperties(parent.node.left) >= 2;
47
+ return countUrlProperties(parent.node.left) >= 1;
48
48
  }
49
49
  return false;
50
50
  };
@@ -6,6 +6,36 @@ const require = createRequire(import.meta.url);
6
6
  const traverse = require("@babel/traverse").default;
7
7
  const VOID_OPERATOR = "void";
8
8
  const UNDEFINED_NAME = "undefined";
9
+ const isWithinWithStatement = (path) => {
10
+ return path.findParent((parentPath) => parentPath.isWithStatement()) !== null;
11
+ };
12
+ const isSafeVoidArgument = (node) => {
13
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
14
+ return true;
15
+ }
16
+ return (node.type === "NumericLiteral" ||
17
+ node.type === "StringLiteral" ||
18
+ node.type === "BooleanLiteral" ||
19
+ node.type === "NullLiteral" ||
20
+ node.type === "BigIntLiteral" ||
21
+ node.type === "RegExpLiteral" ||
22
+ node.type === "FunctionExpression" ||
23
+ node.type === "ArrowFunctionExpression" ||
24
+ node.type === "ClassExpression");
25
+ };
26
+ const isSafeIdentifierVoidArgument = (path) => {
27
+ if (isWithinWithStatement(path))
28
+ return false;
29
+ const argument = path.node.argument;
30
+ if (argument.type !== "Identifier")
31
+ return false;
32
+ const binding = path.scope.getBinding(argument.name);
33
+ if (!binding)
34
+ return false;
35
+ return (binding.kind === "param" ||
36
+ binding.kind === "hoisted" ||
37
+ binding.kind === "var");
38
+ };
9
39
  const hasVoidOperator = (ast) => {
10
40
  const nodesToVisit = [ast.program];
11
41
  while (nodesToVisit.length > 0) {
@@ -36,13 +66,14 @@ const hasVoidOperator = (ast) => {
36
66
  };
37
67
  export const expandUndefinedLiteralsTransform = {
38
68
  id: "expand-undefined-literals",
39
- description: "Expands void 0 to undefined (when undefined is not shadowed)",
69
+ description: "Expands side-effect-free void expressions to undefined",
40
70
  scope: "file",
41
71
  parallelizable: true,
42
72
  transform(context) {
43
73
  let nodesVisited = 0;
44
74
  let transformationsApplied = 0;
45
75
  for (const fileInfo of getFilesToProcess(context)) {
76
+ const injectedVoidZeroSentinels = new WeakSet();
46
77
  // This string precheck intentionally allows false positives for speed.
47
78
  // `hasVoidOperator` on the current AST prevents false negatives.
48
79
  if (!fileInfo.content.includes(VOID_OPERATOR) &&
@@ -55,9 +86,18 @@ export const expandUndefinedLiteralsTransform = {
55
86
  const { operator, argument } = path.node;
56
87
  if (operator !== VOID_OPERATOR)
57
88
  return;
58
- if (argument.type !== "NumericLiteral")
89
+ if (injectedVoidZeroSentinels.has(path.node) &&
90
+ isWithinWithStatement(path))
91
+ return;
92
+ if (!path.get("argument").isPure()) {
93
+ const voidZeroSentinel = t.unaryExpression(VOID_OPERATOR, t.numericLiteral(0), true);
94
+ injectedVoidZeroSentinels.add(voidZeroSentinel);
95
+ path.replaceWith(t.sequenceExpression([argument, voidZeroSentinel]));
96
+ transformationsApplied++;
59
97
  return;
60
- if (argument.value !== 0)
98
+ }
99
+ if (!isSafeVoidArgument(argument) &&
100
+ !isSafeIdentifierVoidArgument(path))
61
101
  return;
62
102
  if (path.scope.hasBinding(UNDEFINED_NAME, true))
63
103
  return;
@@ -3,9 +3,9 @@
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
5
  "diffSizePercent": 100,
6
- "evaluatedAt": "2026-02-11T07:19:35.922Z",
7
- "changedLines": 12668,
8
- "durationSeconds": 40.302393417,
6
+ "evaluatedAt": "2026-02-23T06:04:26.719Z",
7
+ "changedLines": 12700,
8
+ "durationSeconds": 63.882494208000004,
9
9
  "stableNames": 1357
10
10
  }
11
11
  },
@@ -0,0 +1,5 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { IfStatement, Statement } from "@babel/types";
3
+ type InterruptingBranchCheck = (statementPath: NodePath<Statement>, boundaryIfPath: NodePath<IfStatement>) => boolean;
4
+ export declare function doesSwitchInterruptBoundary(switchPath: NodePath<Statement>, boundaryIfPath: NodePath<IfStatement>, isInterruptingBranch: InterruptingBranchCheck): boolean;
5
+ export {};
@@ -0,0 +1,39 @@
1
+ import { getGuaranteedSwitchCaseIndex } from "./get-guaranteed-switch-case-index.js";
2
+ import { getLastNonEmptyStatementIndex } from "./get-last-non-empty-statement-index.js";
3
+ import { hasNonInterruptingSwitchExitBeforeIndex } from "./has-non-interrupting-switch-exit.js";
4
+ export function doesSwitchInterruptBoundary(switchPath, boundaryIfPath, isInterruptingBranch) {
5
+ if (!switchPath.isSwitchStatement())
6
+ return false;
7
+ const casePaths = switchPath.get("cases");
8
+ if (casePaths.length === 0)
9
+ return false;
10
+ const caseInterrupts = getCaseInterruptions(casePaths, boundaryIfPath, isInterruptingBranch);
11
+ const hasDefaultCase = casePaths.some((casePath) => casePath.node.test === null);
12
+ if (hasDefaultCase)
13
+ return caseInterrupts.every(Boolean);
14
+ const guaranteedCaseIndex = getGuaranteedSwitchCaseIndex(switchPath);
15
+ if (guaranteedCaseIndex === undefined)
16
+ return false;
17
+ return caseInterrupts[guaranteedCaseIndex] === true;
18
+ }
19
+ function getCaseInterruptions(casePaths, boundaryIfPath, isInterruptingBranch) {
20
+ const caseInterrupts = Array.from({ length: casePaths.length }).fill(false);
21
+ for (let index = casePaths.length - 1; index >= 0; index -= 1) {
22
+ const casePath = casePaths[index];
23
+ if (!casePath)
24
+ continue;
25
+ const consequentPaths = casePath.get("consequent");
26
+ const lastStatementIndex = getLastNonEmptyStatementIndex(consequentPaths);
27
+ if (lastStatementIndex !== undefined &&
28
+ isInterruptingBranch(consequentPaths[lastStatementIndex], boundaryIfPath) &&
29
+ !hasNonInterruptingSwitchExitBeforeIndex(consequentPaths, lastStatementIndex, boundaryIfPath, isInterruptingBranch)) {
30
+ caseInterrupts[index] = true;
31
+ continue;
32
+ }
33
+ if (caseInterrupts[index + 1] &&
34
+ !hasNonInterruptingSwitchExitBeforeIndex(consequentPaths, consequentPaths.length, boundaryIfPath, isInterruptingBranch)) {
35
+ caseInterrupts[index] = true;
36
+ }
37
+ }
38
+ return caseInterrupts;
39
+ }
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { SwitchStatement } from "@babel/types";
3
+ export declare function getGuaranteedSwitchCaseIndex(switchPath: NodePath<SwitchStatement>): number | undefined;
@@ -0,0 +1,58 @@
1
+ export function getGuaranteedSwitchCaseIndex(switchPath) {
2
+ const discriminantValue = getStaticExpressionValue(switchPath.get("discriminant"));
3
+ if (discriminantValue === undefined)
4
+ return undefined;
5
+ const casePaths = switchPath.get("cases");
6
+ for (const [index, casePath] of casePaths.entries()) {
7
+ const testPath = casePath.get("test");
8
+ if (!testPath.node)
9
+ continue;
10
+ const testValue = getStaticExpressionValue(testPath);
11
+ if (testValue !== discriminantValue)
12
+ continue;
13
+ return index;
14
+ }
15
+ return undefined;
16
+ }
17
+ function getStaticExpressionValue(expressionPath) {
18
+ try {
19
+ const evaluation = expressionPath.evaluate();
20
+ if (evaluation.confident) {
21
+ if (typeof evaluation.value === "string")
22
+ return evaluation.value;
23
+ if (typeof evaluation.value === "number")
24
+ return evaluation.value;
25
+ if (typeof evaluation.value === "boolean")
26
+ return evaluation.value;
27
+ }
28
+ }
29
+ catch {
30
+ // Some expressions cannot be statically evaluated by Babel.
31
+ }
32
+ const expression = expressionPath.node;
33
+ if (expression.type === "StringLiteral")
34
+ return expression.value;
35
+ if (expression.type === "NumericLiteral")
36
+ return expression.value;
37
+ if (expression.type === "BooleanLiteral")
38
+ return expression.value;
39
+ if (expression.type === "NullLiteral")
40
+ return undefined;
41
+ if (expression.type !== "UnaryExpression")
42
+ return undefined;
43
+ if (expression.operator === "void")
44
+ return undefined;
45
+ const argumentValue = getStaticExpressionValue(expressionPath.get("argument"));
46
+ if (argumentValue === undefined)
47
+ return undefined;
48
+ if (expression.operator === "+" && typeof argumentValue === "number") {
49
+ return argumentValue;
50
+ }
51
+ if (expression.operator === "-" && typeof argumentValue === "number") {
52
+ return -argumentValue;
53
+ }
54
+ if (expression.operator === "!" && typeof argumentValue === "boolean") {
55
+ return !argumentValue;
56
+ }
57
+ return undefined;
58
+ }
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Statement } from "@babel/types";
3
+ export declare function getLastNonEmptyStatementIndex(statements: NodePath<Statement>[]): number | undefined;
@@ -0,0 +1,28 @@
1
+ export function getLastNonEmptyStatementIndex(statements) {
2
+ for (let index = statements.length - 1; index >= 0; index -= 1) {
3
+ const statementPath = statements[index];
4
+ if (!statementPath?.node)
5
+ continue;
6
+ if (statementPath.isEmptyStatement())
7
+ continue;
8
+ if (isIgnorableTrailingStatement(statementPath))
9
+ continue;
10
+ return index;
11
+ }
12
+ return undefined;
13
+ }
14
+ function isIgnorableTrailingStatement(statementPath) {
15
+ if (statementPath.isVariableDeclaration())
16
+ return true;
17
+ if (statementPath.isClassDeclaration())
18
+ return true;
19
+ if (statementPath.isFunctionDeclaration())
20
+ return true;
21
+ if (statementPath.isTSDeclareFunction())
22
+ return true;
23
+ if (statementPath.isTSTypeAliasDeclaration())
24
+ return true;
25
+ if (statementPath.isTSInterfaceDeclaration())
26
+ return true;
27
+ return false;
28
+ }
@@ -0,0 +1,5 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { IfStatement, Statement } from "@babel/types";
3
+ type InterruptingBranchCheck = (statementPath: NodePath<Statement>, boundaryIfPath: NodePath<IfStatement>) => boolean;
4
+ export declare function hasNonInterruptingSwitchExitBeforeIndex(statements: NodePath<Statement>[], stopIndex: number, boundaryIfPath: NodePath<IfStatement>, isInterruptingBranch: InterruptingBranchCheck): boolean;
5
+ export {};
@@ -0,0 +1,44 @@
1
+ export function hasNonInterruptingSwitchExitBeforeIndex(statements, stopIndex, boundaryIfPath, isInterruptingBranch) {
2
+ for (let index = 0; index < stopIndex; index += 1) {
3
+ const statementPath = statements[index];
4
+ if (!statementPath?.node)
5
+ continue;
6
+ if (statementPath.isEmptyStatement())
7
+ continue;
8
+ if (hasPotentialNonInterruptingSwitchExit(statementPath, boundaryIfPath, isInterruptingBranch)) {
9
+ return true;
10
+ }
11
+ if (isInterruptingBranch(statementPath, boundaryIfPath))
12
+ return false;
13
+ }
14
+ return false;
15
+ }
16
+ function hasPotentialNonInterruptingSwitchExit(statementPath, boundaryIfPath, isInterruptingBranch) {
17
+ if (statementPath.isBreakStatement() || statementPath.isContinueStatement()) {
18
+ return !isInterruptingBranch(statementPath, boundaryIfPath);
19
+ }
20
+ if (statementPath.isIfStatement()) {
21
+ const consequentPath = statementPath.get("consequent");
22
+ if (hasPotentialNonInterruptingSwitchExit(consequentPath, boundaryIfPath, isInterruptingBranch)) {
23
+ return true;
24
+ }
25
+ const alternatePath = statementPath.get("alternate");
26
+ if (!alternatePath.node)
27
+ return false;
28
+ return hasPotentialNonInterruptingSwitchExit(alternatePath, boundaryIfPath, isInterruptingBranch);
29
+ }
30
+ if (statementPath.isLabeledStatement()) {
31
+ return hasPotentialNonInterruptingSwitchExit(statementPath.get("body"), boundaryIfPath, isInterruptingBranch);
32
+ }
33
+ if (statementPath.isBlockStatement()) {
34
+ const bodyPaths = statementPath.get("body");
35
+ for (const bodyPath of bodyPaths) {
36
+ if (hasPotentialNonInterruptingSwitchExit(bodyPath, boundaryIfPath, isInterruptingBranch)) {
37
+ return true;
38
+ }
39
+ if (isInterruptingBranch(bodyPath, boundaryIfPath))
40
+ return false;
41
+ }
42
+ }
43
+ return false;
44
+ }
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { IfStatement, Statement } from "@babel/types";
3
+ export declare function isInterruptingBranch(statementPath: NodePath<Statement>, boundaryIfPath: NodePath<IfStatement>): boolean;
@@ -0,0 +1,124 @@
1
+ import { doesJumpInterruptBoundary } from "./does-jump-interrupt-boundary.js";
2
+ import { doesSwitchInterruptBoundary } from "./does-switch-interrupt-boundary.js";
3
+ export function isInterruptingBranch(statementPath, boundaryIfPath) {
4
+ if (isInterruptingStatement(statementPath, boundaryIfPath))
5
+ return true;
6
+ if (statementPath.isIfStatement()) {
7
+ const alternatePath = statementPath.get("alternate");
8
+ if (!alternatePath.node)
9
+ return false;
10
+ return (isInterruptingBranch(statementPath.get("consequent"), boundaryIfPath) &&
11
+ isInterruptingBranch(alternatePath, boundaryIfPath));
12
+ }
13
+ if (statementPath.isTryStatement()) {
14
+ const blockPath = statementPath.get("block");
15
+ const finalizerPath = statementPath.get("finalizer");
16
+ if (finalizerPath.node &&
17
+ isInterruptingBranch(finalizerPath, boundaryIfPath)) {
18
+ return true;
19
+ }
20
+ if (!isInterruptingBranch(blockPath, boundaryIfPath)) {
21
+ return false;
22
+ }
23
+ const handlerPath = statementPath.get("handler");
24
+ if (!handlerPath.node)
25
+ return true;
26
+ return isInterruptingBranch(handlerPath.get("body"), boundaryIfPath);
27
+ }
28
+ if (statementPath.isSwitchStatement()) {
29
+ return doesSwitchInterruptBoundary(statementPath, boundaryIfPath, isInterruptingBranch);
30
+ }
31
+ if (statementPath.isLabeledStatement()) {
32
+ return isInterruptingBranch(statementPath.get("body"), boundaryIfPath);
33
+ }
34
+ if (!statementPath.isBlockStatement())
35
+ return false;
36
+ const firstPath = getFirstNonEmptyStatementPath(statementPath.get("body"));
37
+ if (firstPath && isInterruptingBranch(firstPath, boundaryIfPath)) {
38
+ return true;
39
+ }
40
+ const linearInterruptingPath = getInterruptingPathAfterLinearPrefix(statementPath.get("body"), boundaryIfPath);
41
+ if (linearInterruptingPath)
42
+ return true;
43
+ const lastPath = getLastNonEmptyStatementPath(statementPath.get("body"));
44
+ if (!lastPath)
45
+ return false;
46
+ return isInterruptingBranch(lastPath, boundaryIfPath);
47
+ }
48
+ function isInterruptingStatement(statementPath, boundaryIfPath) {
49
+ if (statementPath.isReturnStatement())
50
+ return true;
51
+ if (statementPath.isThrowStatement())
52
+ return true;
53
+ if (statementPath.isBreakStatement() || statementPath.isContinueStatement()) {
54
+ return doesJumpInterruptBoundary(statementPath, boundaryIfPath);
55
+ }
56
+ return false;
57
+ }
58
+ function getLastNonEmptyStatementPath(statements) {
59
+ for (let index = statements.length - 1; index >= 0; index -= 1) {
60
+ const statementPath = statements[index];
61
+ if (!statementPath?.node)
62
+ continue;
63
+ if (statementPath.isEmptyStatement())
64
+ continue;
65
+ if (isIgnorableTrailingStatement(statementPath))
66
+ continue;
67
+ return statementPath;
68
+ }
69
+ return undefined;
70
+ }
71
+ function getFirstNonEmptyStatementPath(statements) {
72
+ for (const statementPath of statements) {
73
+ if (statementPath.isEmptyStatement())
74
+ continue;
75
+ return statementPath;
76
+ }
77
+ return undefined;
78
+ }
79
+ function getInterruptingPathAfterLinearPrefix(statements, boundaryIfPath) {
80
+ for (const statementPath of statements) {
81
+ if (statementPath.isEmptyStatement())
82
+ continue;
83
+ if (isInterruptingBranch(statementPath, boundaryIfPath))
84
+ return statementPath;
85
+ if (isStatementGuaranteedToContinue(statementPath))
86
+ continue;
87
+ return undefined;
88
+ }
89
+ return undefined;
90
+ }
91
+ function isStatementGuaranteedToContinue(statementPath) {
92
+ if (statementPath.isExpressionStatement())
93
+ return true;
94
+ if (statementPath.isDebuggerStatement())
95
+ return true;
96
+ if (statementPath.isVariableDeclaration())
97
+ return true;
98
+ if (statementPath.isClassDeclaration())
99
+ return true;
100
+ if (statementPath.isFunctionDeclaration())
101
+ return true;
102
+ if (statementPath.isTSDeclareFunction())
103
+ return true;
104
+ if (statementPath.isTSTypeAliasDeclaration())
105
+ return true;
106
+ if (statementPath.isTSInterfaceDeclaration())
107
+ return true;
108
+ return false;
109
+ }
110
+ function isIgnorableTrailingStatement(statementPath) {
111
+ if (statementPath.isVariableDeclaration())
112
+ return true;
113
+ if (statementPath.isClassDeclaration())
114
+ return true;
115
+ if (statementPath.isFunctionDeclaration())
116
+ return true;
117
+ if (statementPath.isTSDeclareFunction())
118
+ return true;
119
+ if (statementPath.isTSTypeAliasDeclaration())
120
+ return true;
121
+ if (statementPath.isTSInterfaceDeclaration())
122
+ return true;
123
+ return false;
124
+ }
@@ -3,9 +3,9 @@
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
5
  "diffSizePercent": 100,
6
- "evaluatedAt": "2026-02-12T09:41:18.443Z",
7
- "changedLines": 6643,
8
- "durationSeconds": 44.4973085,
6
+ "evaluatedAt": "2026-02-23T06:47:04.680Z",
7
+ "changedLines": 6872,
8
+ "durationSeconds": 40.238378,
9
9
  "stableNames": 1357
10
10
  }
11
11
  },
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { getBindingIdentifiers } from "@babel/types";
3
3
  import { getFilesToProcess, } from "../../core/types.js";
4
- import { doesJumpInterruptBoundary } from "./does-jump-interrupt-boundary.js";
4
+ import { isInterruptingBranch } from "./is-interrupting-branch.js";
5
5
  const require = createRequire(import.meta.url);
6
6
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
7
7
  const traverse = require("@babel/traverse").default;
@@ -21,7 +21,7 @@ export const removeRedundantElseTransform = {
21
21
  if (!alternate)
22
22
  return;
23
23
  const interrupts = isInterruptingBranch(path.get("consequent"), path);
24
- if (!interrupts)
24
+ if (!interrupts && !isRedundantElseIfChain(path.node))
25
25
  return;
26
26
  // Remove alternate
27
27
  // eslint-disable-next-line unicorn/no-null
@@ -48,53 +48,65 @@ export const removeRedundantElseTransform = {
48
48
  });
49
49
  },
50
50
  };
51
- function isInterruptingBranch(statementPath, boundaryIfPath) {
52
- if (isInterruptingStatement(statementPath, boundaryIfPath))
53
- return true;
54
- if (statementPath.isIfStatement()) {
55
- const alternatePath = statementPath.get("alternate");
56
- if (!alternatePath.node)
51
+ function isRedundantElseIfChain(ifNode) {
52
+ const outerTest = getStrictIdentifierLiteralEquality(ifNode.test);
53
+ if (!outerTest)
54
+ return false;
55
+ const outerLiteralValue = outerTest.literalValue;
56
+ let alternateNode = ifNode.alternate;
57
+ if (!alternateNode)
58
+ return false;
59
+ for (;;) {
60
+ if (alternateNode.type !== "IfStatement")
57
61
  return false;
58
- return (isInterruptingBranch(statementPath.get("consequent"), boundaryIfPath) &&
59
- isInterruptingBranch(alternatePath, boundaryIfPath));
60
- }
61
- if (statementPath.isTryStatement()) {
62
- const blockPath = statementPath.get("block");
63
- if (!isInterruptingBranch(blockPath, boundaryIfPath)) {
62
+ const alternateTest = getStrictIdentifierLiteralEquality(alternateNode.test);
63
+ if (!alternateTest)
64
64
  return false;
65
- }
66
- const handlerPath = statementPath.get("handler");
67
- if (!handlerPath.node)
65
+ if (alternateTest.identifierName !== outerTest.identifierName)
66
+ return false;
67
+ if (Object.is(alternateTest.literalValue, outerLiteralValue))
68
+ return false;
69
+ if (!alternateNode.alternate)
68
70
  return true;
69
- return isInterruptingBranch(handlerPath.get("body"), boundaryIfPath);
71
+ alternateNode = alternateNode.alternate;
70
72
  }
71
- if (!statementPath.isBlockStatement())
72
- return false;
73
- const lastPath = getLastNonEmptyStatementPath(statementPath.get("body"));
74
- if (!lastPath)
75
- return false;
76
- return isInterruptingBranch(lastPath, boundaryIfPath);
77
73
  }
78
- function isInterruptingStatement(statementPath, boundaryIfPath) {
79
- if (statementPath.isReturnStatement())
74
+ function getStrictIdentifierLiteralEquality(testExpression) {
75
+ if (testExpression.type !== "BinaryExpression")
76
+ return undefined;
77
+ if (testExpression.operator !== "===")
78
+ return undefined;
79
+ const left = testExpression.left;
80
+ const right = testExpression.right;
81
+ if (left.type === "Identifier" && isLiteralExpression(right)) {
82
+ return {
83
+ identifierName: left.name,
84
+ literalValue: getLiteralValue(right),
85
+ };
86
+ }
87
+ if (right.type === "Identifier" && isLiteralExpression(left)) {
88
+ return {
89
+ identifierName: right.name,
90
+ literalValue: getLiteralValue(left),
91
+ };
92
+ }
93
+ return undefined;
94
+ }
95
+ function isLiteralExpression(expression) {
96
+ if (expression.type === "StringLiteral")
80
97
  return true;
81
- if (statementPath.isThrowStatement())
98
+ if (expression.type === "NumericLiteral")
99
+ return true;
100
+ if (expression.type === "BooleanLiteral")
101
+ return true;
102
+ if (expression.type === "NullLiteral")
82
103
  return true;
83
- if (statementPath.isBreakStatement() || statementPath.isContinueStatement()) {
84
- return doesJumpInterruptBoundary(statementPath, boundaryIfPath);
85
- }
86
104
  return false;
87
105
  }
88
- function getLastNonEmptyStatementPath(statements) {
89
- for (let index = statements.length - 1; index >= 0; index -= 1) {
90
- const statementPath = statements[index];
91
- if (!statementPath?.node)
92
- continue;
93
- if (statementPath.isEmptyStatement())
94
- continue;
95
- return statementPath;
96
- }
97
- return undefined;
106
+ function getLiteralValue(literal) {
107
+ if (literal.type === "NullLiteral")
108
+ return undefined;
109
+ return literal.value;
98
110
  }
99
111
  function shouldPreserveAlternateBlockScope(ifPath, alternate) {
100
112
  const lexicalNames = getTopLevelLexicalNames(alternate);
@@ -1,15 +1,9 @@
1
1
  import { isAssignedToOnHandlerProperty, isCatchRejectionHandlerArgument, isDirectCallOfBinding, isErrorEventHandlerArgument, isErrorEventHandlerRemovalArgument, isThenFulfillmentHandlerArgument, isThenRejectionHandlerArgument, } from "../rename-promise-executor-parameters-v2/promise-executor-heuristics.js";
2
- import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
3
- const isExecutorParameterEligible = (parameter) => {
4
- if (!isValidBindingIdentifier(parameter.name))
5
- return false;
6
- return true;
7
- };
8
2
  export const getExecutorFunctionIfEligible = (path) => {
9
3
  // Intentionally does not require `new Promise(...)`: minified bundles often alias
10
4
  // Promise constructors, and matching `new X((a, b) => ...)` keeps this transform aligned
11
5
  // with v1/v2 promise-executor heuristics elsewhere in the codebase.
12
- if (path.node.arguments.length !== 1)
6
+ if (path.node.arguments.length === 0)
13
7
  return;
14
8
  const executor = path.get("arguments.0");
15
9
  if (!executor.isFunctionExpression() &&
@@ -26,10 +20,6 @@ export const getExecutorFunctionIfEligible = (path) => {
26
20
  return;
27
21
  if (secondParameter.type !== "Identifier")
28
22
  return;
29
- if (!isExecutorParameterEligible(firstParameter))
30
- return;
31
- if (!isExecutorParameterEligible(secondParameter))
32
- return;
33
23
  return executor;
34
24
  };
35
25
  const isArrayElementReference = (referencePath) => {
@@ -3,9 +3,9 @@
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
5
  "diffSizePercent": 99.99246065403827,
6
- "evaluatedAt": "2026-02-17T13:40:54.302Z",
7
- "changedLines": 1682,
8
- "durationSeconds": 217.667605528,
6
+ "evaluatedAt": "2026-02-22T17:56:04.941Z",
7
+ "changedLines": 1740,
8
+ "durationSeconds": 238.50724275000002,
9
9
  "stableNames": 1359
10
10
  }
11
11
  },