miniread 1.1.0 → 1.2.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.
@@ -5,7 +5,7 @@ type TransformManifestEntry = {
5
5
  scope: Transform["scope"];
6
6
  parallelizable: boolean;
7
7
  diffReductionImpact: number;
8
- enabledByDefault: boolean;
8
+ recommended: boolean;
9
9
  evaluatedAt?: string;
10
10
  notes?: string;
11
11
  supersededBy?: string;
@@ -6,7 +6,7 @@ const TransformManifestEntrySchema = z
6
6
  scope: z.union([z.literal("file"), z.literal("project")]),
7
7
  parallelizable: z.boolean(),
8
8
  diffReductionImpact: z.number(),
9
- enabledByDefault: z.boolean(),
9
+ recommended: z.boolean(),
10
10
  evaluatedAt: z.iso.datetime().optional(),
11
11
  notes: z.string().optional(),
12
12
  supersededBy: z.string().min(1).optional(),
@@ -45,7 +45,7 @@ export const updateTransformManifestFromEvaluation = async (options) => {
45
45
  }
46
46
  if (isBaselineNone) {
47
47
  const recommendedIds = manifest.transforms
48
- .filter((entry) => entry.enabledByDefault)
48
+ .filter((entry) => entry.recommended)
49
49
  .map((entry) => entry.id);
50
50
  const testUniqueIds = uniqueIds(testIds);
51
51
  const recommendedUniqueIds = uniqueIds(recommendedIds);
@@ -0,0 +1,4 @@
1
+ import type { Binding, Scope } from "@babel/traverse";
2
+ export declare const getTargetName: (bindingScope: Scope, binding: Binding, options: {
3
+ baseName: string;
4
+ }) => string | undefined;
@@ -0,0 +1,25 @@
1
+ import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
2
+ // Cap retries to avoid pathological loops while handling large bundles.
3
+ const MAX_CANDIDATES = 1000;
4
+ export const getTargetName = (bindingScope, binding, options) => {
5
+ const { baseName } = options;
6
+ if (baseName.length === 0)
7
+ return;
8
+ const programScope = bindingScope.getProgramParent();
9
+ for (let index = 0; index < MAX_CANDIDATES; index++) {
10
+ const candidate = index === 0 ? baseName : `${baseName}${index + 1}`;
11
+ if (!isValidBindingIdentifier(candidate))
12
+ continue;
13
+ // Avoid shadowing bindings in parent scopes.
14
+ if (bindingScope.hasBinding(candidate))
15
+ continue;
16
+ if (Object.hasOwn(programScope.globals, candidate))
17
+ continue;
18
+ const wouldBeShadowed = binding.referencePaths.some((referencePath) => referencePath.scope !== bindingScope &&
19
+ referencePath.scope.hasBinding(candidate));
20
+ if (wouldBeShadowed)
21
+ continue;
22
+ return candidate;
23
+ }
24
+ return;
25
+ };
@@ -0,0 +1 @@
1
+ export declare const isValidBindingIdentifier: (name: string) => boolean;
@@ -0,0 +1,10 @@
1
+ import { isIdentifierName, isKeyword, isStrictBindReservedWord, } from "@babel/helper-validator-identifier";
2
+ export const isValidBindingIdentifier = (name) => {
3
+ if (!isIdentifierName(name))
4
+ return false;
5
+ if (isKeyword(name))
6
+ return false;
7
+ if (isStrictBindReservedWord(name, true))
8
+ return false;
9
+ return true;
10
+ };
@@ -0,0 +1,2 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ export declare const renameTimeoutIdsTransform: Transform;
@@ -0,0 +1,86 @@
1
+ import { createRequire } from "node:module";
2
+ import { getTargetName } from "../rename-binding/get-target-name.js";
3
+ const require = createRequire(import.meta.url);
4
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5
+ const traverse = require("@babel/traverse").default;
6
+ const BASE_NAME = "timeoutId";
7
+ const isSetTimeoutCall = (path, init) => {
8
+ if (init.callee.type !== "Identifier")
9
+ return false;
10
+ if (init.callee.name !== "setTimeout")
11
+ return false;
12
+ // If setTimeout is locally bound (imported or declared), semantics may differ.
13
+ if (path.scope.hasBinding("setTimeout", true))
14
+ return false;
15
+ return true;
16
+ };
17
+ const isClearTimeoutCallArgument = (referencePath, bindingName) => {
18
+ if (!referencePath.isIdentifier())
19
+ return false;
20
+ const callPath = referencePath.parentPath;
21
+ if (!callPath.isCallExpression())
22
+ return false;
23
+ const call = callPath.node;
24
+ if (call.callee.type !== "Identifier")
25
+ return false;
26
+ if (call.callee.name !== "clearTimeout")
27
+ return false;
28
+ // If clearTimeout is locally bound (imported or declared), semantics may differ.
29
+ if (callPath.scope.hasBinding("clearTimeout", true))
30
+ return false;
31
+ const argument0 = call.arguments[0];
32
+ if (argument0?.type !== "Identifier")
33
+ return false;
34
+ if (argument0 !== referencePath.node)
35
+ return false;
36
+ if (argument0.name !== bindingName)
37
+ return false;
38
+ return true;
39
+ };
40
+ export const renameTimeoutIdsTransform = {
41
+ id: "rename-timeout-ids",
42
+ description: "Renames setTimeout handle variables to timeoutId/timeoutId2/... when usage is only clearTimeout(...)",
43
+ scope: "file",
44
+ parallelizable: true,
45
+ transform(context) {
46
+ const { projectGraph } = context;
47
+ let nodesVisited = 0;
48
+ let transformationsApplied = 0;
49
+ for (const [, fileInfo] of projectGraph.files) {
50
+ traverse(fileInfo.ast, {
51
+ VariableDeclarator(path) {
52
+ nodesVisited++;
53
+ const id = path.node.id;
54
+ if (id.type !== "Identifier")
55
+ return;
56
+ if (id.name.length > 2)
57
+ return;
58
+ const init = path.node.init;
59
+ if (init?.type !== "CallExpression")
60
+ return;
61
+ if (!isSetTimeoutCall(path, init))
62
+ return;
63
+ const binding = path.scope.getBinding(id.name);
64
+ if (!binding)
65
+ return;
66
+ if (!binding.constant)
67
+ return;
68
+ if (binding.referencePaths.length === 0)
69
+ return;
70
+ if (!binding.referencePaths.every((referencePath) => isClearTimeoutCallArgument(referencePath, id.name)))
71
+ return;
72
+ const targetName = getTargetName(path.scope, binding, {
73
+ baseName: BASE_NAME,
74
+ });
75
+ if (!targetName)
76
+ return;
77
+ if (id.name === targetName)
78
+ return;
79
+ path.scope.rename(id.name, targetName);
80
+ transformationsApplied++;
81
+ },
82
+ });
83
+ }
84
+ return Promise.resolve({ nodesVisited, transformationsApplied });
85
+ },
86
+ };
@@ -3,7 +3,7 @@ import * as z from "zod";
3
3
  const TransformManifestEntry = z
4
4
  .object({
5
5
  id: z.string().min(1),
6
- enabledByDefault: z.boolean(),
6
+ recommended: z.boolean(),
7
7
  })
8
8
  .loose();
9
9
  const TransformManifest = z
@@ -14,6 +14,6 @@ const TransformManifest = z
14
14
  const transformManifest = TransformManifest.parse(transformManifestJson);
15
15
  export const transformPresets = {
16
16
  recommended: transformManifest.transforms
17
- .filter((transform) => transform.enabledByDefault)
17
+ .filter((transform) => transform.recommended)
18
18
  .map((transform) => transform.id),
19
19
  };
@@ -9,6 +9,7 @@ import { renameCatchParametersTransform } from "./rename-catch-parameters/rename
9
9
  import { renameDestructuredAliasesTransform } from "./rename-destructured-aliases/rename-destructured-aliases-transform.js";
10
10
  import { renameLoopIndexVariablesTransform } from "./rename-loop-index-variables/rename-loop-index-variables-transform.js";
11
11
  import { renamePromiseExecutorParametersTransform } from "./rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js";
12
+ import { renameTimeoutIdsTransform } from "./rename-timeout-ids/rename-timeout-ids-transform.js";
12
13
  import { renameUseReferenceGuardsTransform } from "./rename-use-reference-guards/rename-use-reference-guards-transform.js";
13
14
  import { splitVariableDeclarationsTransform } from "./split-variable-declarations/split-variable-declarations-transform.js";
14
15
  export const transformRegistry = {
@@ -23,6 +24,7 @@ export const transformRegistry = {
23
24
  [renameDestructuredAliasesTransform.id]: renameDestructuredAliasesTransform,
24
25
  [renameLoopIndexVariablesTransform.id]: renameLoopIndexVariablesTransform,
25
26
  [renamePromiseExecutorParametersTransform.id]: renamePromiseExecutorParametersTransform,
27
+ [renameTimeoutIdsTransform.id]: renameTimeoutIdsTransform,
26
28
  [renameUseReferenceGuardsTransform.id]: renameUseReferenceGuardsTransform,
27
29
  [splitVariableDeclarationsTransform.id]: splitVariableDeclarationsTransform,
28
30
  };
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.1.0",
5
+ "version": "1.2.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",
@@ -6,7 +6,7 @@
6
6
  "scope": "file",
7
7
  "parallelizable": true,
8
8
  "diffReductionImpact": 0,
9
- "enabledByDefault": true,
9
+ "recommended": true,
10
10
  "evaluatedAt": "2026-01-21T15:01:23.708Z",
11
11
  "notes": "Improves readability but does not reduce diffs (boolean literals are already deterministic)"
12
12
  },
@@ -16,7 +16,7 @@
16
16
  "scope": "file",
17
17
  "parallelizable": true,
18
18
  "diffReductionImpact": 0,
19
- "enabledByDefault": true,
19
+ "recommended": true,
20
20
  "evaluatedAt": "2026-01-21T16:27:42.316Z",
21
21
  "notes": "Auto-added by evaluation script."
22
22
  },
@@ -26,7 +26,7 @@
26
26
  "scope": "file",
27
27
  "parallelizable": true,
28
28
  "diffReductionImpact": 0,
29
- "enabledByDefault": true,
29
+ "recommended": true,
30
30
  "evaluatedAt": "2026-01-21T21:06:19.512Z",
31
31
  "notes": "Auto-added by evaluation script."
32
32
  },
@@ -36,7 +36,7 @@
36
36
  "scope": "file",
37
37
  "parallelizable": true,
38
38
  "diffReductionImpact": 0.0007738623280043599,
39
- "enabledByDefault": true,
39
+ "recommended": true,
40
40
  "evaluatedAt": "2026-01-23T07:48:59.087Z",
41
41
  "notes": "Auto-added by evaluation script."
42
42
  },
@@ -46,7 +46,7 @@
46
46
  "scope": "file",
47
47
  "parallelizable": true,
48
48
  "diffReductionImpact": 0.000943734546346775,
49
- "enabledByDefault": true,
49
+ "recommended": true,
50
50
  "evaluatedAt": "2026-01-22T12:49:10.952Z",
51
51
  "notes": "Auto-added by evaluation script. Measured with baseline none: 0.09%."
52
52
  },
@@ -56,7 +56,7 @@
56
56
  "scope": "file",
57
57
  "parallelizable": true,
58
58
  "diffReductionImpact": -0.0012834789830316051,
59
- "enabledByDefault": false,
59
+ "recommended": false,
60
60
  "evaluatedAt": "2026-01-22T15:19:04.615Z",
61
61
  "notes": "Auto-added by evaluation script. Measured with baseline none: -0.13%. Superseded by expand-sequence-expressions-v2 in the recommended preset."
62
62
  },
@@ -66,7 +66,7 @@
66
66
  "scope": "file",
67
67
  "parallelizable": true,
68
68
  "diffReductionImpact": -0.01282535248485317,
69
- "enabledByDefault": false,
69
+ "recommended": false,
70
70
  "evaluatedAt": "2026-01-22T17:15:48.528Z",
71
71
  "notes": "Auto-added by evaluation script. Measured with baseline none: -1.28%. Enabled for readability even when line diffs increase. Kept separate from expand-return-sequence to avoid modifying existing transforms and to allow independent enabling."
72
72
  },
@@ -76,7 +76,7 @@
76
76
  "scope": "file",
77
77
  "parallelizable": true,
78
78
  "diffReductionImpact": -0.014316453068081048,
79
- "enabledByDefault": false,
79
+ "recommended": false,
80
80
  "evaluatedAt": "2026-01-22T18:22:48.609Z",
81
81
  "notes": "Superseded by expand-sequence-expressions-v3."
82
82
  },
@@ -86,7 +86,7 @@
86
86
  "scope": "file",
87
87
  "parallelizable": true,
88
88
  "diffReductionImpact": -0.014391951831788763,
89
- "enabledByDefault": true,
89
+ "recommended": true,
90
90
  "evaluatedAt": "2026-01-23T08:21:23.662Z",
91
91
  "notes": "Supersedes expand-sequence-expressions-v2 in the recommended preset. Measured with baseline none: -1.44%. Enabled for readability even when line diffs increase."
92
92
  },
@@ -96,17 +96,27 @@
96
96
  "scope": "file",
97
97
  "parallelizable": true,
98
98
  "diffReductionImpact": 0,
99
- "enabledByDefault": true,
99
+ "recommended": true,
100
100
  "evaluatedAt": "2026-01-22T17:03:19.826Z",
101
101
  "notes": "Auto-added by evaluation script."
102
102
  },
103
+ {
104
+ "id": "rename-timeout-ids",
105
+ "description": "Renames setTimeout handle variables to timeoutId/timeoutId2/... when usage is only clearTimeout(...)",
106
+ "scope": "file",
107
+ "parallelizable": true,
108
+ "diffReductionImpact": 0.00003774938185385768,
109
+ "recommended": false,
110
+ "evaluatedAt": "2026-01-23T10:29:23.279Z",
111
+ "notes": "Measured with baseline none: 0.00%. Not enabled by default (readability-only)."
112
+ },
103
113
  {
104
114
  "id": "split-variable-declarations",
105
115
  "description": "Splits multi-declarator variable declarations into separate statements",
106
116
  "scope": "file",
107
117
  "parallelizable": true,
108
118
  "diffReductionImpact": -0.0027651422207961573,
109
- "enabledByDefault": true,
119
+ "recommended": true,
110
120
  "evaluatedAt": "2026-01-23T05:45:27.981Z",
111
121
  "notes": "Auto-added by evaluation script. Measured with baseline none: -0.28%. Enabled in the recommended preset for readability and to normalize variable declarations even when line diffs increase."
112
122
  },
@@ -116,7 +126,7 @@
116
126
  "scope": "file",
117
127
  "parallelizable": true,
118
128
  "diffReductionImpact": -0.00007549876370771536,
119
- "enabledByDefault": false,
129
+ "recommended": false,
120
130
  "evaluatedAt": "2026-01-23T07:48:29.356Z",
121
131
  "notes": "Largely superseded by expand-sequence-expressions-v3 (which also expands throw sequences), but kept for isolated runs and comparison."
122
132
  },
@@ -126,7 +136,7 @@
126
136
  "scope": "file",
127
137
  "parallelizable": true,
128
138
  "diffReductionImpact": 0,
129
- "enabledByDefault": true,
139
+ "recommended": true,
130
140
  "evaluatedAt": "2026-01-22T21:39:53.578Z",
131
141
  "notes": "Auto-added by evaluation script."
132
142
  }