miniread 1.112.0 → 1.114.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 (20) hide show
  1. package/dist/transforms/_generated/catalog.js +7 -0
  2. package/dist/transforms/preset-stats.json +1 -1
  3. package/dist/transforms-by-id/rename-array-accumulator-variables/manifest.json +5 -5
  4. package/dist/transforms-by-id/rename-array-accumulator-variables/rename-array-accumulator-variables-transform.js +14 -12
  5. package/dist/transforms-by-id/rename-command-result-variables/manifest.json +2 -2
  6. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/deferred-executor-heuristics.d.ts +2 -1
  7. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/deferred-executor-heuristics.js +22 -8
  8. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/manifest.json +4 -4
  9. package/dist/transforms-by-id/rename-deferred-resolve-parameters-v2/rename-deferred-resolve-parameters-v2-transform.js +16 -15
  10. package/dist/transforms-by-id/rename-git-command-results/manifest.json +13 -0
  11. package/dist/transforms-by-id/rename-git-command-results/rename-git-command-results-transform.d.ts +2 -0
  12. package/dist/transforms-by-id/rename-git-command-results/rename-git-command-results-transform.js +110 -0
  13. package/dist/transforms-by-id/rename-promise-executor-parameters-v2/get-promise-executor-rename-plan.d.ts +2 -2
  14. package/dist/transforms-by-id/rename-promise-executor-parameters-v2/get-promise-executor-rename-plan.js +41 -27
  15. package/dist/transforms-by-id/rename-promise-executor-parameters-v2/manifest.json +3 -3
  16. package/dist/transforms-by-id/rename-promise-executor-parameters-v2/promise-executor-heuristics.js +3 -3
  17. package/dist/transforms-by-id/rename-promise-executor-parameters-v2/rename-promise-executor-parameters-v2-transform.js +4 -0
  18. package/dist/transforms-by-id/rename-timeout-ids/manifest.json +9 -9
  19. package/dist/transforms-by-id/rename-timeout-ids/rename-timeout-ids-transform.js +44 -21
  20. package/package.json +1 -1
@@ -74,6 +74,8 @@ import { renameFileReaderVariablesTransform } from "../../transforms-by-id/renam
74
74
  import renameFileReaderVariablesManifest from "../../transforms-by-id/rename-file-reader-variables/manifest.json" with { type: "json" };
75
75
  import { renameFsSyncVariablesTransform } from "../../transforms-by-id/rename-fs-sync-variables/rename-fs-sync-variables-transform.js";
76
76
  import renameFsSyncVariablesManifest from "../../transforms-by-id/rename-fs-sync-variables/manifest.json" with { type: "json" };
77
+ import { renameGitCommandResultsTransform } from "../../transforms-by-id/rename-git-command-results/rename-git-command-results-transform.js";
78
+ import renameGitCommandResultsManifest from "../../transforms-by-id/rename-git-command-results/manifest.json" with { type: "json" };
77
79
  import { renameHttpMethodParametersTransform } from "../../transforms-by-id/rename-http-method-parameters/rename-http-method-parameters-transform.js";
78
80
  import renameHttpMethodParametersManifest from "../../transforms-by-id/rename-http-method-parameters/manifest.json" with { type: "json" };
79
81
  import { renameHttpServerParametersTransform } from "../../transforms-by-id/rename-http-server-parameters/rename-http-server-parameters-transform.js";
@@ -380,6 +382,11 @@ export const generatedTransformCatalog = [
380
382
  transform: renameFsSyncVariablesTransform,
381
383
  manifest: renameFsSyncVariablesManifest,
382
384
  },
385
+ {
386
+ id: renameGitCommandResultsTransform.id,
387
+ transform: renameGitCommandResultsTransform,
388
+ manifest: renameGitCommandResultsManifest,
389
+ },
383
390
  {
384
391
  id: renameHttpMethodParametersTransform.id,
385
392
  transform: renameHttpMethodParametersTransform,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "recommended": {
3
- "diffSizePercent": 26.062100767282757,
3
+ "diffSizePercent": 26.05643119023321,
4
4
  "notes": "Measured with baseline none: 26.06% of original diff."
5
5
  }
6
6
  }
@@ -1,11 +1,11 @@
1
1
  {
2
- "notes": "Renames variables initialized as empty arrays with .push() usage to stable/readable names.",
2
+ "notes": "Renames variables initialized as empty arrays with .push()/.unshift() usage to stable/readable names.",
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
- "diffSizePercent": 99.95275352458707,
6
- "evaluatedAt": "2026-02-12T07:32:40.071Z",
7
- "changedLines": 9226,
8
- "durationSeconds": 44.1652115,
5
+ "diffSizePercent": 99.94708394753752,
6
+ "evaluatedAt": "2026-02-17T08:05:01.672Z",
7
+ "changedLines": 9338,
8
+ "durationSeconds": 41.067913833,
9
9
  "stableNames": 1360
10
10
  }
11
11
  },
@@ -6,22 +6,24 @@ const require = createRequire(import.meta.url);
6
6
  const traverse = require("@babel/traverse").default;
7
7
  const BASE_NAME = "results";
8
8
  /**
9
- * Checks whether a binding reference is a `.push()` call on the variable.
10
- * Matches: `X.push(...)` where X is the binding.
9
+ * Checks whether a binding reference is an accumulator mutation call on the variable.
10
+ * Matches: `X.push(...)` or `X.unshift(...)` where X is the binding.
11
11
  */
12
- const isPushCallReference = (referencePath) => {
12
+ const isAccumulatorMutationReference = (referencePath) => {
13
13
  const memberPath = referencePath.parentPath;
14
14
  if (!memberPath?.isMemberExpression())
15
15
  return false;
16
- // Must be X.push (not X[push] or X["push"])
16
+ // Must be X.push/X.unshift (not X[push] or X["push"])
17
17
  if (memberPath.node.computed)
18
18
  return false;
19
19
  if (memberPath.node.object !== referencePath.node)
20
20
  return false;
21
21
  const property = memberPath.node.property;
22
- if (property.type !== "Identifier" || property.name !== "push")
22
+ if (property.type !== "Identifier")
23
23
  return false;
24
- // The member expression must be called: X.push(...)
24
+ if (property.name !== "push" && property.name !== "unshift")
25
+ return false;
26
+ // The member expression must be called: X.push(...) / X.unshift(...)
25
27
  const callPath = memberPath.parentPath;
26
28
  if (!callPath.isCallExpression())
27
29
  return false;
@@ -30,15 +32,15 @@ const isPushCallReference = (referencePath) => {
30
32
  return true;
31
33
  };
32
34
  /**
33
- * Checks whether a binding has at least one `.push()` call reference,
35
+ * Checks whether a binding has at least one array mutation call reference,
34
36
  * confirming the accumulator pattern.
35
37
  */
36
- const hasAtLeastOnePushCall = (referencePaths) => {
37
- return referencePaths.some((path) => isPushCallReference(path));
38
+ const hasAtLeastOneAccumulatorMutation = (referencePaths) => {
39
+ return referencePaths.some((path) => isAccumulatorMutationReference(path));
38
40
  };
39
41
  export const renameArrayAccumulatorVariablesTransform = {
40
42
  id: "rename-array-accumulator-variables",
41
- description: "Renames variables initialized as empty arrays with .push() usage to $results/$results2/... or results/results2/...",
43
+ description: "Renames variables initialized as empty arrays with .push()/.unshift() usage to $results/$results2/... or results/results2/...",
42
44
  scope: "file",
43
45
  parallelizable: true,
44
46
  transform(context) {
@@ -72,8 +74,8 @@ export const renameArrayAccumulatorVariablesTransform = {
72
74
  // Must have at least one reference
73
75
  if (binding.referencePaths.length === 0)
74
76
  return;
75
- // Must have at least one .push() call to confirm accumulator pattern
76
- if (!hasAtLeastOnePushCall(binding.referencePaths))
77
+ // Must have at least one array mutation call to confirm accumulator pattern
78
+ if (!hasAtLeastOneAccumulatorMutation(binding.referencePaths))
77
79
  return;
78
80
  group.add({
79
81
  scope: path.scope,
@@ -2,9 +2,9 @@
2
2
  "evaluations": {
3
3
  "claude-code-2.1.10:claude-code-2.1.11": {
4
4
  "diffSizePercent": 100,
5
- "evaluatedAt": "2026-02-13T08:14:27.481Z",
5
+ "evaluatedAt": "2026-02-14T12:53:18.669Z",
6
6
  "changedLines": 338,
7
- "durationSeconds": 39.400678667,
7
+ "durationSeconds": 38.783343792,
8
8
  "stableNames": 1359
9
9
  }
10
10
  },
@@ -1,5 +1,6 @@
1
1
  import type { NodePath } from "@babel/traverse";
2
2
  import type { ArrowFunctionExpression, FunctionExpression, NewExpression } from "@babel/types";
3
3
  export declare const getExecutorFunctionIfEligible: (path: NodePath<NewExpression>) => NodePath<FunctionExpression | ArrowFunctionExpression> | undefined;
4
- export declare const isStoredReference: (referencePath: NodePath) => boolean;
4
+ export declare const isResolvePromiseLikeReference: (referencePath: NodePath) => boolean;
5
+ export declare const isRejectPromiseLikeReference: (referencePath: NodePath) => boolean;
5
6
  export declare const wouldShadowReferencedOuterBinding: (executorPath: NodePath<FunctionExpression | ArrowFunctionExpression>, targetName: string) => boolean;
@@ -1,3 +1,4 @@
1
+ import { isAssignedToOnHandlerProperty, isCatchRejectionHandlerArgument, isDirectCallOfBinding, isErrorEventHandlerArgument, isErrorEventHandlerRemovalArgument, isThenFulfillmentHandlerArgument, isThenRejectionHandlerArgument, } from "../rename-promise-executor-parameters-v2/promise-executor-heuristics.js";
1
2
  import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
2
3
  const isExecutorParameterEligible = (parameter) => {
3
4
  if (!isValidBindingIdentifier(parameter.name))
@@ -63,14 +64,27 @@ const isVariableDeclaratorInitReference = (referencePath) => {
63
64
  return false;
64
65
  return referencePath.key === "init";
65
66
  };
66
- export const isStoredReference = (referencePath) => {
67
- // Keep this narrow on purpose: direct call-argument passing (e.g. `queue.push(resolve)`) is
68
- // excluded as lower-confidence. We still allow identifiers inside array/object literals
69
- // (e.g. `queue.push([resolve, reject])`) because those references are explicit storage sites.
70
- return (isArrayElementReference(referencePath) ||
71
- isObjectPropertyValueReference(referencePath) ||
72
- isAssignmentValueReference(referencePath) ||
73
- isVariableDeclaratorInitReference(referencePath));
67
+ const isStoredReference = (referencePath) => isArrayElementReference(referencePath) ||
68
+ isObjectPropertyValueReference(referencePath) ||
69
+ isAssignmentValueReference(referencePath) ||
70
+ isVariableDeclaratorInitReference(referencePath);
71
+ export const isResolvePromiseLikeReference = (referencePath) => {
72
+ if (!referencePath.isIdentifier())
73
+ return false;
74
+ return (isStoredReference(referencePath) ||
75
+ isDirectCallOfBinding(referencePath) ||
76
+ isThenFulfillmentHandlerArgument(referencePath));
77
+ };
78
+ export const isRejectPromiseLikeReference = (referencePath) => {
79
+ if (!referencePath.isIdentifier())
80
+ return false;
81
+ return (isStoredReference(referencePath) ||
82
+ isDirectCallOfBinding(referencePath) ||
83
+ isThenRejectionHandlerArgument(referencePath) ||
84
+ isCatchRejectionHandlerArgument(referencePath) ||
85
+ isAssignedToOnHandlerProperty(referencePath) ||
86
+ isErrorEventHandlerArgument(referencePath) ||
87
+ isErrorEventHandlerRemovalArgument(referencePath));
74
88
  };
75
89
  export const wouldShadowReferencedOuterBinding = (executorPath, targetName) => {
76
90
  const parentBinding = executorPath.scope.getBinding(targetName);
@@ -2,10 +2,10 @@
2
2
  "notes": "Renames deferred promise executor parameters to $resolve/$reject when both are only stored.",
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
- "diffSizePercent": 100,
6
- "evaluatedAt": "2026-02-10T20:26:59.436Z",
7
- "changedLines": 92,
8
- "durationSeconds": 36.222931665999994,
5
+ "diffSizePercent": 99.99244056393393,
6
+ "evaluatedAt": "2026-02-16T08:21:48.869Z",
7
+ "changedLines": 1670,
8
+ "durationSeconds": 40.99589079100001,
9
9
  "stableNames": 1359
10
10
  }
11
11
  },
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { isStableRenamed } from "../../core/stable-naming.js";
3
3
  import { getFilesToProcess, } from "../../core/types.js";
4
- import { getExecutorFunctionIfEligible, isStoredReference, wouldShadowReferencedOuterBinding, } from "./deferred-executor-heuristics.js";
4
+ import { getExecutorFunctionIfEligible, isRejectPromiseLikeReference, isResolvePromiseLikeReference, wouldShadowReferencedOuterBinding, } from "./deferred-executor-heuristics.js";
5
5
  import { hasExecutorLocalDynamicNameLookup } from "./has-executor-local-dynamic-name-lookup.js";
6
6
  import { canRenameBindingSafely } from "./rename-binding-if-safe.js";
7
7
  const require = createRequire(import.meta.url);
@@ -10,7 +10,7 @@ const traverse = require("@babel/traverse").default;
10
10
  const STABLE_PREFIX = "$";
11
11
  export const renameDeferredResolveParametersV2Transform = {
12
12
  id: "rename-deferred-resolve-parameters-v2",
13
- description: "Renames deferred promise resolve/reject parameters to $resolve/$reject when each parameter is only stored or unused",
13
+ description: "Renames deferred promise resolve/reject parameters to $resolve/$reject when safe",
14
14
  scope: "file",
15
15
  parallelizable: true,
16
16
  transform(context) {
@@ -60,28 +60,29 @@ export const renameDeferredResolveParametersV2Transform = {
60
60
  const rejectBinding = bindingScope.getBinding(rejectParameter.name);
61
61
  if (!resolveBinding || !rejectBinding)
62
62
  return;
63
- if (resolveBinding.constantViolations.length > 0)
64
- return;
65
- if (rejectBinding.constantViolations.length > 0)
66
- return;
67
- const resolveHasReferences = resolveBinding.referencePaths.length > 0;
68
- const rejectHasReferences = rejectBinding.referencePaths.length > 0;
69
63
  const resolveReferencePaths = resolveBinding.referencePaths;
70
64
  const rejectReferencePaths = rejectBinding.referencePaths;
71
65
  const hasUsageSignal = resolveReferencePaths.length > 0 || rejectReferencePaths.length > 0;
72
66
  if (!hasUsageSignal)
73
67
  return;
74
- const resolveIsStoredOnly = resolveReferencePaths.every((referencePath) => referencePath.isIdentifier() && isStoredReference(referencePath));
75
- if (!resolveIsStoredOnly)
68
+ const resolveHasOnlyPromiseLikeReferences = resolveReferencePaths.every((referencePath) => isResolvePromiseLikeReference(referencePath));
69
+ if (!resolveHasOnlyPromiseLikeReferences)
76
70
  return;
77
- const rejectIsStoredOnly = rejectReferencePaths.every((referencePath) => referencePath.isIdentifier() && isStoredReference(referencePath));
78
- if (!rejectIsStoredOnly)
71
+ const rejectHasOnlyPromiseLikeReferences = rejectReferencePaths.every((referencePath) => isRejectPromiseLikeReference(referencePath));
72
+ if (!rejectHasOnlyPromiseLikeReferences)
79
73
  return;
80
- // Avoid introducing semantic names for never-used parameters.
81
- const wantsResolveRename = resolveHasReferences && resolveParameter.name !== targetResolveName;
82
- const wantsRejectRename = rejectHasReferences && rejectParameter.name !== targetRejectName;
74
+ const wantsResolveRename = resolveParameter.name !== targetResolveName;
75
+ const wantsRejectRename = rejectParameter.name !== targetRejectName;
83
76
  if (!wantsResolveRename && !wantsRejectRename)
84
77
  return;
78
+ if (wantsResolveRename &&
79
+ resolveBinding.constantViolations.length > 0) {
80
+ return;
81
+ }
82
+ if (wantsRejectRename &&
83
+ rejectBinding.constantViolations.length > 0) {
84
+ return;
85
+ }
85
86
  // Shadowing checks require a full traversal of the executor, so only do them for parameters
86
87
  // that actually need renaming.
87
88
  const resolveWouldShadow = wantsResolveRename &&
@@ -0,0 +1,13 @@
1
+ {
2
+ "notes": "Pending evaluation.",
3
+ "evaluations": {
4
+ "claude-code-2.1.10:claude-code-2.1.11": {
5
+ "diffSizePercent": 100,
6
+ "evaluatedAt": "2026-02-14T12:52:39.042Z",
7
+ "changedLines": 350,
8
+ "durationSeconds": 39.629334792,
9
+ "stableNames": 1365
10
+ }
11
+ },
12
+ "recommended": false
13
+ }
@@ -0,0 +1,2 @@
1
+ import { type Transform } from "../../core/types.js";
2
+ export declare const renameGitCommandResultsTransform: Transform;
@@ -0,0 +1,110 @@
1
+ import { createRequire } from "node:module";
2
+ import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
3
+ import { getFilesToProcess, } from "../../core/types.js";
4
+ const require = createRequire(import.meta.url);
5
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
+ const traverse = require("@babel/traverse").default;
7
+ const RESULT_PROPERTY_BASE_NAMES = {
8
+ code: "gitExitCode",
9
+ signal: "gitSignal",
10
+ stderr: "gitStderr",
11
+ stdout: "gitStdout",
12
+ };
13
+ const GIT_RESULT_BASE_NAME = "gitResult";
14
+ const isResultPropertyName = (name) => Object.hasOwn(RESULT_PROPERTY_BASE_NAMES, name);
15
+ const isGitCommandCall = (call) => {
16
+ const firstArgument = call.arguments[0];
17
+ if (!firstArgument)
18
+ return false;
19
+ if (firstArgument.type !== "StringLiteral")
20
+ return false;
21
+ return firstArgument.value === "git";
22
+ };
23
+ const getCallFromAwaitExpression = (expression) => {
24
+ if (expression.argument.type !== "CallExpression")
25
+ return undefined;
26
+ return expression.argument;
27
+ };
28
+ const getCallFromInitializer = (init) => {
29
+ if (!init)
30
+ return undefined;
31
+ if (init.type === "CallExpression")
32
+ return init;
33
+ if (init.type !== "AwaitExpression")
34
+ return undefined;
35
+ return getCallFromAwaitExpression(init);
36
+ };
37
+ const addObjectPatternRenames = (path, objectPattern, group) => {
38
+ for (const property of objectPattern.properties) {
39
+ if (property.type !== "ObjectProperty")
40
+ continue;
41
+ addObjectPropertyRename(path, property, group);
42
+ }
43
+ };
44
+ const addObjectPropertyRename = (path, property, group) => {
45
+ if (property.computed)
46
+ return;
47
+ if (property.key.type !== "Identifier")
48
+ return;
49
+ if (!isResultPropertyName(property.key.name))
50
+ return;
51
+ const value = property.value;
52
+ if (value.type !== "Identifier")
53
+ return;
54
+ if (isStableRenamed(value.name))
55
+ return;
56
+ const binding = path.scope.getBinding(value.name);
57
+ if (!binding)
58
+ return;
59
+ group.add({
60
+ scope: path.scope,
61
+ currentName: value.name,
62
+ baseName: RESULT_PROPERTY_BASE_NAMES[property.key.name],
63
+ });
64
+ };
65
+ const addIdentifierRename = (path, identifier, group) => {
66
+ if (isStableRenamed(identifier.name))
67
+ return;
68
+ const binding = path.scope.getBinding(identifier.name);
69
+ if (!binding)
70
+ return;
71
+ if (binding.referencePaths.length === 0)
72
+ return;
73
+ group.add({
74
+ scope: path.scope,
75
+ currentName: identifier.name,
76
+ baseName: GIT_RESULT_BASE_NAME,
77
+ });
78
+ };
79
+ export const renameGitCommandResultsTransform = {
80
+ id: "rename-git-command-results",
81
+ description: "Renames git command result bindings and destructured stdout/stderr/code/signal aliases to stable names",
82
+ scope: "file",
83
+ parallelizable: true,
84
+ transform(context) {
85
+ let nodesVisited = 0;
86
+ let transformationsApplied = 0;
87
+ for (const fileInfo of getFilesToProcess(context)) {
88
+ const group = new RenameGroup();
89
+ traverse(fileInfo.ast, {
90
+ VariableDeclarator(path) {
91
+ nodesVisited++;
92
+ const call = getCallFromInitializer(path.node.init);
93
+ if (!call)
94
+ return;
95
+ if (!isGitCommandCall(call))
96
+ return;
97
+ if (path.node.id.type === "Identifier") {
98
+ addIdentifierRename(path, path.node.id, group);
99
+ return;
100
+ }
101
+ if (path.node.id.type !== "ObjectPattern")
102
+ return;
103
+ addObjectPatternRenames(path, path.node.id, group);
104
+ },
105
+ });
106
+ transformationsApplied += group.apply();
107
+ }
108
+ return Promise.resolve({ nodesVisited, transformationsApplied });
109
+ },
110
+ };
@@ -3,9 +3,9 @@ import type { ArrowFunctionExpression, FunctionExpression } from "@babel/types";
3
3
  type PromiseExecutorRenamePlan = {
4
4
  bindingScope: Scope;
5
5
  fromResolveName: string;
6
- fromRejectName: string;
6
+ fromRejectName: string | undefined;
7
7
  targetResolveName: string;
8
- targetRejectName: string;
8
+ targetRejectName: string | undefined;
9
9
  wantsResolveRename: boolean;
10
10
  wantsRejectRename: boolean;
11
11
  };
@@ -5,37 +5,42 @@ import { isAssignedToOnHandlerProperty, isCatchRejectionHandlerArgument, isDirec
5
5
  const STABLE_PREFIX = "$";
6
6
  export const getPromiseExecutorRenamePlan = (executorPath) => {
7
7
  const parameters = executorPath.node.params;
8
- if (parameters.length !== 2)
8
+ if (parameters.length !== 1 && parameters.length !== 2)
9
9
  return;
10
10
  const [firstParameter, secondParameter] = parameters;
11
- if (!firstParameter || !secondParameter)
11
+ if (!firstParameter)
12
12
  return;
13
13
  if (firstParameter.type !== "Identifier")
14
14
  return;
15
- if (secondParameter.type !== "Identifier")
16
- return;
17
15
  const resolveParameter = firstParameter;
18
- const rejectParameter = secondParameter;
16
+ const rejectParameter = secondParameter?.type === "Identifier" ? secondParameter : undefined;
17
+ if (parameters.length === 2 && !rejectParameter)
18
+ return;
19
19
  // Non-strict scripts can allow duplicate parameter names like `function (a, a) {}`.
20
20
  // Babel models these as a single binding, so we can't safely rename parameters independently.
21
- if (resolveParameter.name === rejectParameter.name)
21
+ if (resolveParameter.name === rejectParameter?.name)
22
22
  return;
23
23
  const targetResolveName = `${STABLE_PREFIX}resolve`;
24
- const targetRejectName = `${STABLE_PREFIX}reject`;
24
+ const targetRejectName = rejectParameter
25
+ ? `${STABLE_PREFIX}reject`
26
+ : undefined;
25
27
  // Skip already-stable names, unless they already match the target stable names.
26
28
  // This allows completing a partially-stable executor like `($resolve, e)` → `($resolve, $reject)`.
27
29
  if (isStableRenamed(resolveParameter.name) &&
28
30
  resolveParameter.name !== targetResolveName) {
29
31
  return;
30
32
  }
31
- if (isStableRenamed(rejectParameter.name) &&
33
+ if (rejectParameter &&
34
+ isStableRenamed(rejectParameter.name) &&
32
35
  rejectParameter.name !== targetRejectName) {
33
36
  return;
34
37
  }
35
38
  const bindingScope = executorPath.scope;
36
39
  const resolveBinding = bindingScope.getBinding(resolveParameter.name);
37
- const rejectBinding = bindingScope.getBinding(rejectParameter.name);
38
- if (!resolveBinding || !rejectBinding)
40
+ const rejectBinding = rejectParameter
41
+ ? bindingScope.getBinding(rejectParameter.name)
42
+ : undefined;
43
+ if (!resolveBinding || (rejectParameter && !rejectBinding))
39
44
  return;
40
45
  const resolveIsCalledOrThenHandled = resolveBinding.referencePaths.some((referencePath) => isDirectCallOfBinding(referencePath) ||
41
46
  isThenFulfillmentHandlerArgument(referencePath));
@@ -47,24 +52,30 @@ export const getPromiseExecutorRenamePlan = (executorPath) => {
47
52
  // unlike `reject` as an `"error"` handler, those patterns are lower-confidence and easier to misclassify.
48
53
  if (!resolveIsOnlyCalledOrThenHandled)
49
54
  return;
50
- const rejectIsCalledOrHandled = rejectBinding.referencePaths.some((referencePath) => isDirectCallOfBinding(referencePath) ||
51
- isThenRejectionHandlerArgument(referencePath) ||
52
- isCatchRejectionHandlerArgument(referencePath));
53
- const rejectIsOnHandler = rejectBinding.referencePaths.some((referencePath) => isAssignedToOnHandlerProperty(referencePath));
54
- const rejectIsErrorHandler = rejectBinding.referencePaths.some((referencePath) => isErrorEventHandlerArgument(referencePath));
55
- if (!rejectIsCalledOrHandled && !rejectIsOnHandler && !rejectIsErrorHandler) {
56
- return;
55
+ if (rejectBinding) {
56
+ const rejectIsCalledOrHandled = rejectBinding.referencePaths.some((referencePath) => isDirectCallOfBinding(referencePath) ||
57
+ isThenRejectionHandlerArgument(referencePath) ||
58
+ isCatchRejectionHandlerArgument(referencePath));
59
+ const rejectIsOnHandler = rejectBinding.referencePaths.some((referencePath) => isAssignedToOnHandlerProperty(referencePath));
60
+ const rejectIsErrorHandler = rejectBinding.referencePaths.some((referencePath) => isErrorEventHandlerArgument(referencePath));
61
+ if (!rejectIsCalledOrHandled &&
62
+ !rejectIsOnHandler &&
63
+ !rejectIsErrorHandler) {
64
+ return;
65
+ }
66
+ const rejectIsOnlyCalledOrHandlerAssigned = rejectBinding.referencePaths.every((referencePath) => isDirectCallOfBinding(referencePath) ||
67
+ isThenRejectionHandlerArgument(referencePath) ||
68
+ isCatchRejectionHandlerArgument(referencePath) ||
69
+ isAssignedToOnHandlerProperty(referencePath) ||
70
+ isErrorEventHandlerArgument(referencePath) ||
71
+ isErrorEventHandlerRemovalArgument(referencePath));
72
+ if (!rejectIsOnlyCalledOrHandlerAssigned)
73
+ return;
57
74
  }
58
- const rejectIsOnlyCalledOrHandlerAssigned = rejectBinding.referencePaths.every((referencePath) => isDirectCallOfBinding(referencePath) ||
59
- isThenRejectionHandlerArgument(referencePath) ||
60
- isCatchRejectionHandlerArgument(referencePath) ||
61
- isAssignedToOnHandlerProperty(referencePath) ||
62
- isErrorEventHandlerArgument(referencePath) ||
63
- isErrorEventHandlerRemovalArgument(referencePath));
64
- if (!rejectIsOnlyCalledOrHandlerAssigned)
65
- return;
66
75
  const wantsResolveRename = resolveParameter.name !== targetResolveName;
67
- const wantsRejectRename = rejectParameter.name !== targetRejectName;
76
+ const wantsRejectRename = rejectParameter && targetRejectName
77
+ ? rejectParameter.name !== targetRejectName
78
+ : false;
68
79
  // Nothing to do: both parameters are already the stable target names.
69
80
  if (!wantsResolveRename && !wantsRejectRename)
70
81
  return;
@@ -73,9 +84,10 @@ export const getPromiseExecutorRenamePlan = (executorPath) => {
73
84
  const resolveWouldShadow = wantsResolveRename &&
74
85
  wouldShadowReferencedOuterBinding(executorPath, targetResolveName);
75
86
  const rejectWouldShadow = wantsRejectRename &&
87
+ targetRejectName !== undefined &&
76
88
  wouldShadowReferencedOuterBinding(executorPath, targetRejectName);
77
89
  const fromResolveName = resolveParameter.name;
78
- const fromRejectName = rejectParameter.name;
90
+ const fromRejectName = rejectParameter?.name;
79
91
  const canRenameResolve = !wantsResolveRename ||
80
92
  (!resolveWouldShadow &&
81
93
  canRenameBindingSafely(bindingScope, fromResolveName, targetResolveName, {
@@ -83,6 +95,8 @@ export const getPromiseExecutorRenamePlan = (executorPath) => {
83
95
  }));
84
96
  const canRenameReject = !wantsRejectRename ||
85
97
  (!rejectWouldShadow &&
98
+ fromRejectName !== undefined &&
99
+ targetRejectName !== undefined &&
86
100
  canRenameBindingSafely(bindingScope, fromRejectName, targetRejectName, {
87
101
  strictMode: false,
88
102
  }));
@@ -3,9 +3,9 @@
3
3
  "evaluations": {
4
4
  "claude-code-2.1.10:claude-code-2.1.11": {
5
5
  "diffSizePercent": 99.99623032701913,
6
- "evaluatedAt": "2026-02-09T14:05:34.662Z",
7
- "changedLines": 1432,
8
- "durationSeconds": 176.089765559,
6
+ "evaluatedAt": "2026-02-13T08:17:00.590Z",
7
+ "changedLines": 1880,
8
+ "durationSeconds": 184.31310626599998,
9
9
  "stableNames": 1359
10
10
  }
11
11
  },
@@ -31,14 +31,14 @@ export const getExecutorFunctionIfEligible = (path) => {
31
31
  return;
32
32
  }
33
33
  const parameters = executor.node.params;
34
- if (parameters.length !== 2)
34
+ if (parameters.length !== 1 && parameters.length !== 2)
35
35
  return;
36
36
  const [firstParameter, secondParameter] = parameters;
37
- if (!firstParameter || !secondParameter)
37
+ if (!firstParameter)
38
38
  return;
39
39
  if (firstParameter.type !== "Identifier")
40
40
  return;
41
- if (secondParameter.type !== "Identifier")
41
+ if (secondParameter !== undefined && secondParameter.type !== "Identifier")
42
42
  return;
43
43
  // v1 checked that the *source* parameter names are valid identifiers, but this is redundant:
44
44
  // if Babel produced Identifier nodes under `sourceType: "unambiguous"` parsing, then the names
@@ -36,6 +36,10 @@ export const renamePromiseExecutorParametersV2Transform = {
36
36
  renamedResolve = true;
37
37
  }
38
38
  if (renamePlan.wantsRejectRename) {
39
+ if (renamePlan.fromRejectName === undefined ||
40
+ renamePlan.targetRejectName === undefined) {
41
+ return;
42
+ }
39
43
  renamePlan.bindingScope.rename(renamePlan.fromRejectName, renamePlan.targetRejectName);
40
44
  renamedReject = true;
41
45
  }
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
- "notes": "Measured with baseline none: 0.00%. Added to recommended for readability.",
2
+ "notes": "Generalized timer-handle rename transform for setTimeout and setInterval. Measured with baseline none: 0.00%. Added to recommended for readability.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
- "diffSizePercent": 99.99623032701913,
8
- "evaluatedAt": "2026-02-07T21:15:56.221Z",
9
- "changedLines": 154,
10
- "durationSeconds": 159.140358367,
11
- "stableNames": 1358
5
+ "diffSizePercent": 99.99622028196697,
6
+ "evaluatedAt": "2026-02-14T12:54:44.910Z",
7
+ "changedLines": 200,
8
+ "durationSeconds": 38.705412083000006,
9
+ "stableNames": 1359
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -4,18 +4,33 @@ import { getFilesToProcess, } from "../../core/types.js";
4
4
  const require = createRequire(import.meta.url);
5
5
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
6
  const traverse = require("@babel/traverse").default;
7
- const BASE_NAME = "timeoutId";
8
- const isSetTimeoutCall = (path, init) => {
9
- if (init.callee.type !== "Identifier")
10
- return false;
11
- if (init.callee.name !== "setTimeout")
12
- return false;
13
- // If setTimeout is locally bound (imported or declared), semantics may differ.
14
- if (path.scope.hasBinding("setTimeout", true))
15
- return false;
16
- return true;
7
+ const TIMER_HANDLE_RENAME_RULES = [
8
+ {
9
+ setName: "setTimeout",
10
+ clearName: "clearTimeout",
11
+ baseName: "timeoutId",
12
+ batch: "timeout",
13
+ },
14
+ {
15
+ setName: "setInterval",
16
+ clearName: "clearInterval",
17
+ baseName: "intervalId",
18
+ batch: "interval",
19
+ },
20
+ ];
21
+ const getTimerHandleRenameRule = (path, init) => {
22
+ const callee = init.callee;
23
+ if (callee.type !== "Identifier")
24
+ return undefined;
25
+ const renameRule = TIMER_HANDLE_RENAME_RULES.find((rule) => rule.setName === callee.name);
26
+ if (!renameRule)
27
+ return undefined;
28
+ // If timer constructor is locally bound (imported or declared), semantics may differ.
29
+ if (path.scope.hasBinding(renameRule.setName, true))
30
+ return undefined;
31
+ return renameRule;
17
32
  };
18
- const isClearTimeoutCallArgument = (referencePath, bindingName) => {
33
+ const isMatchingClearCallArgument = (referencePath, bindingName, renameRule) => {
19
34
  if (!referencePath.isIdentifier())
20
35
  return false;
21
36
  const callPath = referencePath.parentPath;
@@ -24,10 +39,10 @@ const isClearTimeoutCallArgument = (referencePath, bindingName) => {
24
39
  const call = callPath.node;
25
40
  if (call.callee.type !== "Identifier")
26
41
  return false;
27
- if (call.callee.name !== "clearTimeout")
42
+ if (call.callee.name !== renameRule.clearName)
28
43
  return false;
29
- // If clearTimeout is locally bound (imported or declared), semantics may differ.
30
- if (callPath.scope.hasBinding("clearTimeout", true))
44
+ // If clear function is locally bound (imported or declared), semantics may differ.
45
+ if (callPath.scope.hasBinding(renameRule.clearName, true))
31
46
  return false;
32
47
  const argument0 = call.arguments[0];
33
48
  if (argument0?.type !== "Identifier")
@@ -40,14 +55,15 @@ const isClearTimeoutCallArgument = (referencePath, bindingName) => {
40
55
  };
41
56
  export const renameTimeoutIdsTransform = {
42
57
  id: "rename-timeout-ids",
43
- description: "Renames setTimeout handle variables to $timeoutId/$timeoutId2/... or timeoutId/timeoutId2/... when usage is only clearTimeout(...)",
58
+ description: "Renames setTimeout/setInterval handle variables to stable timer names when usage is only clearTimeout(...) or clearInterval(...)",
44
59
  scope: "file",
45
60
  parallelizable: true,
46
61
  transform(context) {
47
62
  let nodesVisited = 0;
48
63
  let transformationsApplied = 0;
49
64
  for (const fileInfo of getFilesToProcess(context)) {
50
- const group = new RenameGroup();
65
+ const intervalRenameGroup = new RenameGroup();
66
+ const timeoutRenameGroup = new RenameGroup();
51
67
  traverse(fileInfo.ast, {
52
68
  VariableDeclarator(path) {
53
69
  nodesVisited++;
@@ -60,7 +76,8 @@ export const renameTimeoutIdsTransform = {
60
76
  const init = path.node.init;
61
77
  if (init?.type !== "CallExpression")
62
78
  return;
63
- if (!isSetTimeoutCall(path, init))
79
+ const renameRule = getTimerHandleRenameRule(path, init);
80
+ if (!renameRule)
64
81
  return;
65
82
  const binding = path.scope.getBinding(id.name);
66
83
  if (!binding)
@@ -69,16 +86,22 @@ export const renameTimeoutIdsTransform = {
69
86
  return;
70
87
  if (binding.referencePaths.length === 0)
71
88
  return;
72
- if (!binding.referencePaths.every((referencePath) => isClearTimeoutCallArgument(referencePath, id.name)))
89
+ if (!binding.referencePaths.every((referencePath) => isMatchingClearCallArgument(referencePath, id.name, renameRule)))
73
90
  return;
74
- group.add({
91
+ const renameGroup = renameRule.batch === "interval"
92
+ ? intervalRenameGroup
93
+ : timeoutRenameGroup;
94
+ renameGroup.add({
75
95
  scope: path.scope,
76
96
  currentName: id.name,
77
- baseName: BASE_NAME,
97
+ baseName: renameRule.baseName,
78
98
  });
79
99
  },
80
100
  });
81
- transformationsApplied += group.apply();
101
+ // Preserve pre-generalization ordering: interval handle renames first,
102
+ // timeout handle renames second.
103
+ transformationsApplied += intervalRenameGroup.apply();
104
+ transformationsApplied += timeoutRenameGroup.apply();
82
105
  }
83
106
  return Promise.resolve({ nodesVisited, transformationsApplied });
84
107
  },
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.112.0",
5
+ "version": "1.114.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",