miniread 1.116.0 → 1.116.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 (24) hide show
  1. package/README.md +3 -0
  2. package/dist/transforms/preset-stats.json +2 -2
  3. package/dist/transforms-by-id/rename-platform-win32-flags/add-assignment-win32-rename-candidate.d.ts +14 -0
  4. package/dist/transforms-by-id/rename-platform-win32-flags/add-assignment-win32-rename-candidate.js +57 -0
  5. package/dist/transforms-by-id/rename-platform-win32-flags/is-safe-process-binding.d.ts +1 -2
  6. package/dist/transforms-by-id/rename-platform-win32-flags/manifest.json +8 -8
  7. package/dist/transforms-by-id/rename-platform-win32-flags/rename-platform-win32-flags-transform.js +28 -9
  8. package/dist/transforms-by-id/rename-use-reference-sets/get-set-reference-usage.d.ts +1 -0
  9. package/dist/transforms-by-id/rename-use-reference-sets/get-set-reference-usage.js +127 -42
  10. package/dist/transforms-by-id/rename-use-reference-sets/is-disallowed-member-usage-parent.d.ts +3 -0
  11. package/dist/transforms-by-id/rename-use-reference-sets/is-disallowed-member-usage-parent.js +28 -0
  12. package/dist/transforms-by-id/rename-use-reference-sets/is-use-reference-set-initializer.js +31 -11
  13. package/dist/transforms-by-id/rename-use-reference-sets/manifest.json +6 -6
  14. package/dist/transforms-by-id/rename-use-reference-sets/rename-use-reference-sets-transform.js +4 -2
  15. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-first-program-scope-assignments.d.ts +9 -0
  16. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-first-program-scope-assignments.js +107 -0
  17. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-nested-program-scope-variable-initializers.js +20 -20
  18. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-rename-candidates.js +31 -26
  19. package/dist/transforms-by-id/stabilize-top-level-bindings/has-disallowed-function-class-assignment-reference.d.ts +8 -0
  20. package/dist/transforms-by-id/stabilize-top-level-bindings/has-disallowed-function-class-assignment-reference.js +74 -0
  21. package/dist/transforms-by-id/stabilize-top-level-bindings/manifest.json +9 -9
  22. package/package.json +1 -1
  23. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-first-program-body-assignments.d.ts +0 -12
  24. package/dist/transforms-by-id/stabilize-top-level-bindings/collect-first-program-body-assignments.js +0 -29
package/README.md CHANGED
@@ -102,6 +102,9 @@ Use `miniread` when you need readable JavaScript/TypeScript from minified input
102
102
  Development workflow/tooling documentation lives under `development-workflows/`:
103
103
 
104
104
  - `development-workflows/AGENTS.md` — shared development tooling and workflow helper docs
105
+ - `development-workflows/codex-task-to-worktree.md` — import Codex task patches into local task worktrees
106
+ - `development-workflows/cherry-pick-worktrees-into-main.md` — cherry-pick worktree commits into main, discard conflicts, remove processed worktrees
107
+ - `development-workflows/generalize-overlapping-transforms.md` — pattern for consolidating overlapping transform behavior
105
108
  - `development-workflows/new-transform.md` — transform iteration workflow
106
109
  - `development-workflows/improve-transform-performance.md` — runtime performance optimization workflow
107
110
  - `development-workflows/improve-transform-coverage.md` — coverage improvement workflow
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "recommended": {
3
- "diffSizePercent": 25.52727066560835,
4
- "notes": "Measured with baseline none: 25.53% of original diff."
3
+ "diffSizePercent": 26.033752882035,
4
+ "notes": "Measured with baseline none: 26.03% of original diff."
5
5
  }
6
6
  }
@@ -0,0 +1,14 @@
1
+ import type { NodePath, Scope } from "@babel/traverse";
2
+ import type { AssignmentExpression, BinaryExpression } from "@babel/types";
3
+ import { type RenameGroup } from "../../core/stable-naming.js";
4
+ import type { HashRenameEntry } from "./apply-hash-stable-win32-renames.js";
5
+ type AddAssignmentWin32RenameCandidateInput = {
6
+ getPlatformIdentifierName: (node: BinaryExpression) => string | undefined;
7
+ getWin32BaseName: (node: BinaryExpression) => "isWindows" | "isNotWindows" | undefined;
8
+ group: RenameGroup;
9
+ hashEntries: HashRenameEntry[];
10
+ path: NodePath<AssignmentExpression>;
11
+ scope: Scope;
12
+ };
13
+ export declare const addAssignmentWin32RenameCandidate: ({ getPlatformIdentifierName, getWin32BaseName, group, hashEntries, path, scope, }: AddAssignmentWin32RenameCandidateInput) => void;
14
+ export {};
@@ -0,0 +1,57 @@
1
+ import { isHashBasedStableName, } from "../../core/stable-naming.js";
2
+ import { isSafeProcessBinding } from "./is-safe-process-binding.js";
3
+ const getAssignmentTargetName = (path) => {
4
+ if (path.node.operator !== "=")
5
+ return undefined;
6
+ if (path.node.left.type !== "Identifier")
7
+ return undefined;
8
+ return path.node.left.name;
9
+ };
10
+ const getAssignmentBinaryExpression = (path) => {
11
+ if (path.node.right.type !== "BinaryExpression")
12
+ return undefined;
13
+ return path.node.right;
14
+ };
15
+ export const addAssignmentWin32RenameCandidate = ({ getPlatformIdentifierName, getWin32BaseName, group, hashEntries, path, scope, }) => {
16
+ const assignmentTargetName = getAssignmentTargetName(path);
17
+ if (!assignmentTargetName)
18
+ return;
19
+ const binaryExpression = getAssignmentBinaryExpression(path);
20
+ if (!binaryExpression)
21
+ return;
22
+ const platformIdentifierName = getPlatformIdentifierName(binaryExpression);
23
+ if (!platformIdentifierName)
24
+ return;
25
+ if (!isSafeProcessBinding(path, platformIdentifierName))
26
+ return;
27
+ const baseName = getWin32BaseName(binaryExpression);
28
+ if (!baseName)
29
+ return;
30
+ const binding = scope.getBinding(assignmentTargetName);
31
+ if (!binding)
32
+ return;
33
+ if (binding.constant)
34
+ return;
35
+ if (binding.constantViolations.length !== 1)
36
+ return;
37
+ const firstViolation = binding.constantViolations[0];
38
+ if (!firstViolation)
39
+ return;
40
+ if (firstViolation.node !== path.node)
41
+ return;
42
+ const bindingScope = binding.scope;
43
+ if (isHashBasedStableName(assignmentTargetName)) {
44
+ hashEntries.push({
45
+ baseName,
46
+ binding,
47
+ currentName: assignmentTargetName,
48
+ scope: bindingScope,
49
+ });
50
+ return;
51
+ }
52
+ group.add({
53
+ scope: bindingScope,
54
+ currentName: assignmentTargetName,
55
+ baseName,
56
+ });
57
+ };
@@ -1,3 +1,2 @@
1
1
  import type { NodePath } from "@babel/traverse";
2
- import type { VariableDeclarator } from "@babel/types";
3
- export declare const isSafeProcessBinding: (path: NodePath<VariableDeclarator>, identifierName: string) => boolean;
2
+ export declare const isSafeProcessBinding: (path: NodePath, identifierName: string) => boolean;
@@ -1,13 +1,13 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 50,
4
2
  "evaluations": {
5
3
  "claude-code-2.1.10:claude-code-2.1.11": {
6
- "diffSizePercent": 99.99623032701913,
7
- "evaluatedAt": "2026-02-07T21:15:35.720Z",
8
- "changedLines": 42,
9
- "durationSeconds": 234.291656082,
10
- "stableNames": 1358
4
+ "diffSizePercent": 99.98677098688438,
5
+ "evaluatedAt": "2026-02-21T16:59:41.380Z",
6
+ "changedLines": 62,
7
+ "durationSeconds": 41.748348541,
8
+ "stableNames": 1359
11
9
  }
12
- }
10
+ },
11
+ "recommended": true,
12
+ "recommendedOrder": 50
13
13
  }
@@ -3,6 +3,7 @@ import { isHashBasedStableName, RenameGroup, } from "../../core/stable-naming.js
3
3
  import { getFilesToProcess, } from "../../core/types.js";
4
4
  import { applyHashStableWin32Renames, } from "./apply-hash-stable-win32-renames.js";
5
5
  import { isSafeProcessBinding } from "./is-safe-process-binding.js";
6
+ import { addAssignmentWin32RenameCandidate } from "./add-assignment-win32-rename-candidate.js";
6
7
  const require = createRequire(import.meta.url);
7
8
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
8
9
  const traverse = require("@babel/traverse").default;
@@ -11,11 +12,16 @@ const ESCAPE_CHARACTER = "\\";
11
12
  const isProcessPlatformExpression = (node) => {
12
13
  if (node.type !== "MemberExpression")
13
14
  return false;
14
- if (node.computed)
15
- return false;
16
15
  if (node.object.type !== "Identifier")
17
16
  return false;
18
17
  // We intentionally keep this structural and verify process identity separately.
18
+ if (node.computed) {
19
+ if (node.property.type !== "StringLiteral")
20
+ return false;
21
+ if (node.property.value !== "platform")
22
+ return false;
23
+ return true;
24
+ }
19
25
  if (node.property.type !== "Identifier")
20
26
  return false;
21
27
  if (node.property.name !== "platform")
@@ -28,13 +34,20 @@ const isWin32Literal = (node) => {
28
34
  return node.value === WIN32_LITERAL;
29
35
  };
30
36
  const getWin32BaseName = (node) => {
31
- if (node.operator !== "===" && node.operator !== "!==")
37
+ if (node.operator !== "===" &&
38
+ node.operator !== "!==" &&
39
+ node.operator !== "==" &&
40
+ node.operator !== "!=") {
32
41
  return undefined;
42
+ }
33
43
  const matchesLeft = isProcessPlatformExpression(node.left) && isWin32Literal(node.right);
34
44
  const matchesRight = isProcessPlatformExpression(node.right) && isWin32Literal(node.left);
35
45
  if (!matchesLeft && !matchesRight)
36
46
  return undefined;
37
- return node.operator === "===" ? "isWindows" : "isNotWindows";
47
+ if (node.operator === "===" || node.operator === "==") {
48
+ return "isWindows";
49
+ }
50
+ return "isNotWindows";
38
51
  };
39
52
  const getPlatformIdentifierName = (node) => {
40
53
  if (isProcessPlatformExpression(node.left)) {
@@ -99,11 +112,6 @@ export const renamePlatformWin32FlagsTransform = {
99
112
  return;
100
113
  if (!binding.constant)
101
114
  return;
102
- // Intentionally skip single-declaration dead flags to avoid churn;
103
- // unlike generic comparison-flags, this transform targets a narrow,
104
- // semantically named pattern where dead-code renames add little value.
105
- if (binding.referencePaths.length === 0)
106
- return;
107
115
  if (isHashBasedStableName(id.name)) {
108
116
  hashEntries.push({
109
117
  baseName,
@@ -119,6 +127,17 @@ export const renamePlatformWin32FlagsTransform = {
119
127
  baseName,
120
128
  });
121
129
  },
130
+ AssignmentExpression(path) {
131
+ nodesVisited++;
132
+ addAssignmentWin32RenameCandidate({
133
+ getPlatformIdentifierName,
134
+ getWin32BaseName,
135
+ group,
136
+ hashEntries,
137
+ path,
138
+ scope: path.scope,
139
+ });
140
+ },
122
141
  });
123
142
  transformationsApplied += applyHashStableWin32Renames(hashEntries);
124
143
  transformationsApplied += group.apply();
@@ -3,6 +3,7 @@ type SetReferenceUsage = {
3
3
  isSafe: boolean;
4
4
  hasSetMethodCall: boolean;
5
5
  hasSizeRead: boolean;
6
+ hasCurrentRead: boolean;
6
7
  };
7
8
  export declare const getSetReferenceUsage: (binding: Binding) => SetReferenceUsage;
8
9
  export {};
@@ -1,78 +1,163 @@
1
- const allowedSetMethodCalls = new Set(["add", "has", "delete", "clear"]);
1
+ import { isDisallowedMemberUsageParent } from "./is-disallowed-member-usage-parent.js";
2
+ const allowedSetMethodCalls = new Set([
3
+ "add",
4
+ "has",
5
+ "delete",
6
+ "clear",
7
+ "forEach",
8
+ "entries",
9
+ "keys",
10
+ "values",
11
+ ]);
12
+ const createResult = (isSafe, usage) => {
13
+ return {
14
+ isSafe,
15
+ hasSetMethodCall: usage.hasSetMethodCall,
16
+ hasSizeRead: usage.hasSizeRead,
17
+ hasCurrentRead: usage.hasCurrentRead,
18
+ };
19
+ };
20
+ const getSafeCurrentRead = (hasSetMethodCall, hasSizeRead, hasCurrentRead, currentMemberPath) => {
21
+ const usagePath = currentMemberPath.parentPath;
22
+ if (isDisallowedMemberUsageParent(usagePath, currentMemberPath.node)) {
23
+ return createResult(false, {
24
+ hasSetMethodCall,
25
+ hasSizeRead,
26
+ hasCurrentRead,
27
+ });
28
+ }
29
+ if (!currentMemberPath.isReferenced()) {
30
+ return createResult(false, {
31
+ hasSetMethodCall,
32
+ hasSizeRead,
33
+ hasCurrentRead,
34
+ });
35
+ }
36
+ return createResult(true, {
37
+ hasSetMethodCall,
38
+ hasSizeRead,
39
+ hasCurrentRead: true,
40
+ });
41
+ };
42
+ const getSafeSizeRead = (hasSetMethodCall, hasSizeRead, hasCurrentRead, usagePath) => {
43
+ const sizeParentPath = usagePath.parentPath;
44
+ if (isDisallowedMemberUsageParent(sizeParentPath, usagePath.node)) {
45
+ return createResult(false, {
46
+ hasSetMethodCall,
47
+ hasSizeRead,
48
+ hasCurrentRead,
49
+ });
50
+ }
51
+ if (!usagePath.isReferenced()) {
52
+ return createResult(false, {
53
+ hasSetMethodCall,
54
+ hasSizeRead,
55
+ hasCurrentRead,
56
+ });
57
+ }
58
+ return createResult(true, {
59
+ hasSetMethodCall,
60
+ hasSizeRead: true,
61
+ hasCurrentRead,
62
+ });
63
+ };
2
64
  export const getSetReferenceUsage = (binding) => {
3
65
  let hasSetMethodCall = false;
4
66
  let hasSizeRead = false;
67
+ let hasCurrentRead = false;
5
68
  for (const referencePath of binding.referencePaths) {
6
69
  if (!referencePath.isIdentifier()) {
7
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
70
+ return createResult(false, {
71
+ hasSetMethodCall,
72
+ hasSizeRead,
73
+ hasCurrentRead,
74
+ });
8
75
  }
9
76
  const currentMemberPath = referencePath.parentPath;
10
77
  if (!currentMemberPath.isMemberExpression()) {
11
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
78
+ return createResult(false, {
79
+ hasSetMethodCall,
80
+ hasSizeRead,
81
+ hasCurrentRead,
82
+ });
12
83
  }
13
84
  if (currentMemberPath.node.computed) {
14
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
85
+ return createResult(false, {
86
+ hasSetMethodCall,
87
+ hasSizeRead,
88
+ hasCurrentRead,
89
+ });
15
90
  }
16
91
  const currentProperty = currentMemberPath.get("property");
17
92
  if (!currentProperty.isIdentifier({ name: "current" })) {
18
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
93
+ return createResult(false, {
94
+ hasSetMethodCall,
95
+ hasSizeRead,
96
+ hasCurrentRead,
97
+ });
19
98
  }
20
99
  const usagePath = currentMemberPath.parentPath;
100
+ if (usagePath.isOptionalMemberExpression() &&
101
+ usagePath.node.object === currentMemberPath.node) {
102
+ return createResult(false, {
103
+ hasSetMethodCall,
104
+ hasSizeRead,
105
+ hasCurrentRead,
106
+ });
107
+ }
21
108
  if (!usagePath.isMemberExpression()) {
22
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
109
+ const currentRead = getSafeCurrentRead(hasSetMethodCall, hasSizeRead, hasCurrentRead, currentMemberPath);
110
+ if (!currentRead.isSafe)
111
+ return currentRead;
112
+ hasCurrentRead = currentRead.hasCurrentRead;
113
+ continue;
23
114
  }
24
115
  if (usagePath.node.computed) {
25
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
116
+ return createResult(false, {
117
+ hasSetMethodCall,
118
+ hasSizeRead,
119
+ hasCurrentRead,
120
+ });
26
121
  }
27
122
  const usageProperty = usagePath.get("property");
28
123
  if (!usageProperty.isIdentifier()) {
29
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
124
+ return createResult(false, {
125
+ hasSetMethodCall,
126
+ hasSizeRead,
127
+ hasCurrentRead,
128
+ });
30
129
  }
31
130
  if (allowedSetMethodCalls.has(usageProperty.node.name)) {
32
131
  const callPath = usagePath.parentPath;
33
132
  if (!callPath.isCallExpression()) {
34
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
133
+ return createResult(false, {
134
+ hasSetMethodCall,
135
+ hasSizeRead,
136
+ hasCurrentRead,
137
+ });
35
138
  }
36
139
  if (callPath.node.callee !== usagePath.node) {
37
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
140
+ return createResult(false, {
141
+ hasSetMethodCall,
142
+ hasSizeRead,
143
+ hasCurrentRead,
144
+ });
38
145
  }
39
146
  hasSetMethodCall = true;
40
147
  continue;
41
148
  }
42
149
  if (usageProperty.node.name === "size") {
43
- const sizeParentPath = usagePath.parentPath;
44
- if (sizeParentPath.isAssignmentExpression() &&
45
- sizeParentPath.node.left === usagePath.node) {
46
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
47
- }
48
- if (sizeParentPath.isUpdateExpression() &&
49
- sizeParentPath.node.argument === usagePath.node) {
50
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
51
- }
52
- if (sizeParentPath.isUnaryExpression() &&
53
- sizeParentPath.node.operator === "delete" &&
54
- sizeParentPath.node.argument === usagePath.node) {
55
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
56
- }
57
- if (sizeParentPath.isCallExpression() &&
58
- sizeParentPath.node.callee === usagePath.node) {
59
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
60
- }
61
- if (sizeParentPath.isOptionalCallExpression() &&
62
- sizeParentPath.node.callee === usagePath.node) {
63
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
64
- }
65
- if (sizeParentPath.isNewExpression() &&
66
- sizeParentPath.node.callee === usagePath.node) {
67
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
68
- }
69
- if (!usagePath.isReferenced()) {
70
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
71
- }
72
- hasSizeRead = true;
150
+ const sizeRead = getSafeSizeRead(hasSetMethodCall, hasSizeRead, hasCurrentRead, usagePath);
151
+ if (!sizeRead.isSafe)
152
+ return sizeRead;
153
+ hasSizeRead = sizeRead.hasSizeRead;
73
154
  continue;
74
155
  }
75
- return { isSafe: false, hasSetMethodCall, hasSizeRead };
156
+ return createResult(false, {
157
+ hasSetMethodCall,
158
+ hasSizeRead,
159
+ hasCurrentRead,
160
+ });
76
161
  }
77
- return { isSafe: true, hasSetMethodCall, hasSizeRead };
162
+ return createResult(true, { hasSetMethodCall, hasSizeRead, hasCurrentRead });
78
163
  };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Node } from "@babel/types";
3
+ export declare const isDisallowedMemberUsageParent: (parentPath: NodePath | null, memberNode: Node) => boolean;
@@ -0,0 +1,28 @@
1
+ export const isDisallowedMemberUsageParent = (parentPath, memberNode) => {
2
+ if (!parentPath)
3
+ return true;
4
+ if (parentPath.isAssignmentExpression() &&
5
+ parentPath.node.left === memberNode) {
6
+ return true;
7
+ }
8
+ if (parentPath.isUpdateExpression() &&
9
+ parentPath.node.argument === memberNode) {
10
+ return true;
11
+ }
12
+ if (parentPath.isUnaryExpression() &&
13
+ parentPath.node.operator === "delete" &&
14
+ parentPath.node.argument === memberNode) {
15
+ return true;
16
+ }
17
+ if (parentPath.isCallExpression() && parentPath.node.callee === memberNode) {
18
+ return true;
19
+ }
20
+ if (parentPath.isOptionalCallExpression() &&
21
+ parentPath.node.callee === memberNode) {
22
+ return true;
23
+ }
24
+ if (parentPath.isNewExpression() && parentPath.node.callee === memberNode) {
25
+ return true;
26
+ }
27
+ return false;
28
+ };
@@ -1,3 +1,33 @@
1
+ const isUseReferenceCallee = (callee) => {
2
+ if (!callee)
3
+ return false;
4
+ if (callee.type === "V8IntrinsicIdentifier")
5
+ return false;
6
+ if (callee.type === "Identifier") {
7
+ return callee.name === "useRef";
8
+ }
9
+ if (callee.type === "MemberExpression") {
10
+ if (callee.computed)
11
+ return false;
12
+ if (callee.property.type !== "Identifier")
13
+ return false;
14
+ return callee.property.name === "useRef";
15
+ }
16
+ if (callee.type === "SequenceExpression") {
17
+ const lastExpression = callee.expressions.at(-1);
18
+ return isUseReferenceCallee(lastExpression);
19
+ }
20
+ if (callee.type === "TSAsExpression" || callee.type === "TSTypeAssertion") {
21
+ return isUseReferenceCallee(callee.expression);
22
+ }
23
+ if (callee.type === "TSNonNullExpression") {
24
+ return isUseReferenceCallee(callee.expression);
25
+ }
26
+ if (callee.type === "ParenthesizedExpression") {
27
+ return isUseReferenceCallee(callee.expression);
28
+ }
29
+ return false;
30
+ };
1
31
  export const isUseReferenceSetInitializer = (declarator) => {
2
32
  const init = declarator.init;
3
33
  if (!init)
@@ -30,15 +60,5 @@ export const isUseReferenceSetInitializer = (declarator) => {
30
60
  if (setArgument.type === "ArgumentPlaceholder")
31
61
  return false;
32
62
  }
33
- const callee = init.callee;
34
- if (callee.type === "Identifier") {
35
- return callee.name === "useRef";
36
- }
37
- if (callee.type !== "MemberExpression")
38
- return false;
39
- if (callee.computed)
40
- return false;
41
- if (callee.property.type !== "Identifier")
42
- return false;
43
- return callee.property.name === "useRef";
63
+ return isUseReferenceCallee(init.callee);
44
64
  };
@@ -1,13 +1,13 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "evaluations": {
5
3
  "claude-code-2.1.10:claude-code-2.1.11": {
6
4
  "diffSizePercent": 100,
7
- "evaluatedAt": "2026-02-08T05:50:45.820Z",
8
- "changedLines": 24,
9
- "durationSeconds": 28.251205084000002,
5
+ "evaluatedAt": "2026-02-21T16:46:50.151Z",
6
+ "changedLines": 38,
7
+ "durationSeconds": 32.887656167,
10
8
  "stableNames": 1358
11
9
  }
12
- }
10
+ },
11
+ "recommended": true,
12
+ "recommendedOrder": 100
13
13
  }
@@ -9,7 +9,7 @@ const traverse = require("@babel/traverse").default;
9
9
  const BASE_NAME = "setRef";
10
10
  export const renameUseReferenceSetsTransform = {
11
11
  id: "rename-use-reference-sets",
12
- description: "Renames useRef(new Set()) variables to $setRef/$setRef2/... or setRef/setRef2/... when usage is limited to safe Set operations on .current.",
12
+ description: "Renames useRef(new Set()) variables to $setRef/$setRef2/... or setRef/setRef2/... when usage is limited to safe Set operations or read-only .current access.",
13
13
  scope: "file",
14
14
  parallelizable: true,
15
15
  transform(context) {
@@ -37,7 +37,9 @@ export const renameUseReferenceSetsTransform = {
37
37
  const usage = getSetReferenceUsage(binding);
38
38
  if (!usage.isSafe)
39
39
  return;
40
- if (!usage.hasSetMethodCall && !usage.hasSizeRead)
40
+ if (!usage.hasSetMethodCall &&
41
+ !usage.hasSizeRead &&
42
+ !usage.hasCurrentRead)
41
43
  return;
42
44
  group.add({
43
45
  scope: path.scope,
@@ -0,0 +1,9 @@
1
+ import type { Scope } from "@babel/traverse";
2
+ import type { Node } from "@babel/types";
3
+ export declare const collectFirstProgramScopeAssignments: (options: {
4
+ exportedNames: ReadonlySet<string>;
5
+ programScope: Scope;
6
+ }) => Map<string, {
7
+ rhs: Node;
8
+ statementIndex: number;
9
+ }>;
@@ -0,0 +1,107 @@
1
+ import * as t from "@babel/types";
2
+ import { isStableRenamed } from "../../core/stable-naming.js";
3
+ import { hasDisallowedFunctionClassAssignmentReference } from "./has-disallowed-function-class-assignment-reference.js";
4
+ const ALWAYS_STABLE_REFERENCE_IDENTIFIER_NAMES = [
5
+ "__dirname",
6
+ "__filename",
7
+ "arguments",
8
+ "Buffer",
9
+ "Promise",
10
+ "clearInterval",
11
+ "clearTimeout",
12
+ "console",
13
+ "document",
14
+ "eval",
15
+ "exports",
16
+ "global",
17
+ "globalThis",
18
+ "module",
19
+ "process",
20
+ "queueMicrotask",
21
+ "require",
22
+ "self",
23
+ "setInterval",
24
+ "setTimeout",
25
+ "window",
26
+ ];
27
+ const getTopLevelAssignmentPosition = (path) => {
28
+ let current = path;
29
+ let fromFunctionOrClass = false;
30
+ while (current.parentPath) {
31
+ if (current.isFunction())
32
+ fromFunctionOrClass = true;
33
+ if (current.isClass())
34
+ fromFunctionOrClass = true;
35
+ if (current.parentPath.isProgram()) {
36
+ return typeof current.key === "number"
37
+ ? { statementIndex: current.key, fromFunctionOrClass }
38
+ : undefined;
39
+ }
40
+ current = current.parentPath;
41
+ }
42
+ return undefined;
43
+ };
44
+ export const collectFirstProgramScopeAssignments = (options) => {
45
+ const { exportedNames, programScope } = options;
46
+ const allowedReferenceIdentifierNames = new Set([
47
+ ...Object.keys(programScope.globals),
48
+ ...exportedNames,
49
+ ...ALWAYS_STABLE_REFERENCE_IDENTIFIER_NAMES,
50
+ "Infinity",
51
+ "NaN",
52
+ "undefined",
53
+ ]);
54
+ const firstAssignmentByName = new Map();
55
+ for (const [name, binding] of Object.entries(programScope.bindings)) {
56
+ if (isStableRenamed(name))
57
+ continue;
58
+ if (exportedNames.has(name))
59
+ continue;
60
+ let firstAssignment;
61
+ for (const violation of binding.constantViolations) {
62
+ const assignmentPath = violation.isAssignmentExpression()
63
+ ? violation
64
+ : violation.parentPath?.isAssignmentExpression()
65
+ ? violation.parentPath
66
+ : undefined;
67
+ if (!assignmentPath)
68
+ continue;
69
+ const assignmentExpression = assignmentPath.node;
70
+ if (assignmentExpression.operator !== "=")
71
+ continue;
72
+ if (!t.isIdentifier(assignmentExpression.left, { name }))
73
+ continue;
74
+ const assignmentPosition = getTopLevelAssignmentPosition(assignmentPath);
75
+ if (!assignmentPosition)
76
+ continue;
77
+ if (assignmentPosition.fromFunctionOrClass &&
78
+ hasDisallowedFunctionClassAssignmentReference({
79
+ rhs: assignmentExpression.right,
80
+ currentName: name,
81
+ programScope,
82
+ allowedReferenceIdentifierNames,
83
+ })) {
84
+ continue;
85
+ }
86
+ const statementIndex = assignmentPosition.statementIndex;
87
+ const nodeStart = assignmentExpression.start ?? Number.MAX_SAFE_INTEGER;
88
+ if (!firstAssignment ||
89
+ statementIndex < firstAssignment.statementIndex ||
90
+ (statementIndex === firstAssignment.statementIndex &&
91
+ nodeStart < firstAssignment.nodeStart)) {
92
+ firstAssignment = {
93
+ rhs: assignmentExpression.right,
94
+ statementIndex,
95
+ nodeStart,
96
+ };
97
+ }
98
+ }
99
+ if (!firstAssignment)
100
+ continue;
101
+ firstAssignmentByName.set(name, {
102
+ rhs: firstAssignment.rhs,
103
+ statementIndex: firstAssignment.statementIndex,
104
+ });
105
+ }
106
+ return firstAssignmentByName;
107
+ };
@@ -4,29 +4,29 @@ export const collectNestedProgramScopeVariableInitializers = (options) => {
4
4
  const { node, statementIndex, exportedNames, programScope, firstAssignmentByName, } = options;
5
5
  const results = [];
6
6
  const tryAddFromVariableDeclarator = (variableDeclarator) => {
7
- if (!t.isIdentifier(variableDeclarator.id))
8
- return;
9
- const name = variableDeclarator.id.name;
10
- if (isStableRenamed(name))
11
- return;
12
- if (exportedNames.has(name))
13
- return;
14
7
  if (!variableDeclarator.init)
15
8
  return;
16
- // `var` declarations inside top-level blocks (e.g. `if (...) { var x = ... }`) are
17
- // still program-scope bindings. Only include them if the binding actually belongs
18
- // to the program scope (not a nested function scope).
19
- const binding = programScope.getBinding(name);
20
- if (binding?.scope !== programScope)
21
- return;
22
- const assignmentInfo = firstAssignmentByName.get(name);
23
- if (assignmentInfo && assignmentInfo.statementIndex < statementIndex) {
24
- // Precedence rule: if a binding is written via a direct Program-body assignment
25
- // before a later initializer (including nested `var` redeclarations), prefer the
26
- // first write for stable naming.
27
- return;
9
+ const names = Object.keys(t.getBindingIdentifiers(variableDeclarator.id));
10
+ for (const name of names) {
11
+ if (isStableRenamed(name))
12
+ continue;
13
+ if (exportedNames.has(name))
14
+ continue;
15
+ // `var` declarations inside top-level blocks (e.g. `if (...) { var x = ... }`) are
16
+ // still program-scope bindings. Only include them if the binding actually belongs
17
+ // to the program scope (not a nested function scope).
18
+ const binding = programScope.getBinding(name);
19
+ if (binding?.scope !== programScope)
20
+ continue;
21
+ const assignmentInfo = firstAssignmentByName.get(name);
22
+ if (assignmentInfo && assignmentInfo.statementIndex < statementIndex) {
23
+ // Precedence rule: if a binding is written via an earlier top-level assignment
24
+ // before a later initializer (including nested `var` redeclarations), prefer the
25
+ // first write for stable naming.
26
+ continue;
27
+ }
28
+ results.push({ name, init: variableDeclarator.init });
28
29
  }
29
- results.push({ name, init: variableDeclarator.init });
30
30
  };
31
31
  const visit = (nodeToVisit) => {
32
32
  if (t.isFunction(nodeToVisit))
@@ -1,7 +1,7 @@
1
1
  import * as t from "@babel/types";
2
2
  import { isStableRenamed } from "../../core/stable-naming.js";
3
3
  import { hashFingerprintNode } from "../../core/fingerprint/hash-fingerprint-node.js";
4
- import { collectFirstProgramBodyAssignments } from "./collect-first-program-body-assignments.js";
4
+ import { collectFirstProgramScopeAssignments } from "./collect-first-program-scope-assignments.js";
5
5
  import { collectNestedProgramScopeVariableInitializers } from "./collect-nested-program-scope-variable-initializers.js";
6
6
  import { addRenameCandidate } from "./rename-candidate.js";
7
7
  export const collectRenameCandidates = (options) => {
@@ -14,8 +14,7 @@ export const collectRenameCandidates = (options) => {
14
14
  // / assignment we encounter for that binding.
15
15
  const seenCandidateNames = new Set();
16
16
  let nodesVisited = 0;
17
- const firstAssignmentByName = collectFirstProgramBodyAssignments({
18
- program,
17
+ const firstAssignmentByName = collectFirstProgramScopeAssignments({
19
18
  exportedNames,
20
19
  programScope,
21
20
  });
@@ -36,35 +35,40 @@ export const collectRenameCandidates = (options) => {
36
35
  if (t.isVariableDeclaration(effectiveStmt)) {
37
36
  const variableDeclaration = effectiveStmt;
38
37
  for (const decl of variableDeclaration.declarations) {
39
- if (!t.isIdentifier(decl.id))
40
- continue;
41
- const name = decl.id.name;
42
- if (isStableRenamed(name))
43
- continue;
44
- if (exportedNames.has(name))
38
+ const names = Object.keys(t.getBindingIdentifiers(decl.id));
39
+ if (names.length === 0)
45
40
  continue;
46
41
  if (!decl.init) {
47
42
  // No initializer: if there's a later top-level `name = ...`, we'll pick it up
48
43
  // from `firstAssignmentByName` below.
49
44
  continue;
50
45
  }
51
- const assignmentInfo = firstAssignmentByName.get(name);
52
- if (assignmentInfo && assignmentInfo.statementIndex < statementIndex) {
53
- // Precedence rule: if a binding is written via a direct Program-body assignment
54
- // before a later redeclaration initializer, prefer the first write for stable
55
- // naming. Example: `var a; a = 1; var a = 2;` hashes based on `1`, not `2`.
56
- continue;
57
- }
58
46
  const hash = hashFingerprintNode(decl.init);
59
- addRenameCandidate({
60
- candidates,
61
- hashToNames,
62
- seenCandidateNames,
63
- name,
64
- hash,
65
- kind: variableDeclaration.kind,
66
- scope: programScope,
67
- });
47
+ for (const name of names) {
48
+ if (isStableRenamed(name))
49
+ continue;
50
+ if (exportedNames.has(name))
51
+ continue;
52
+ const assignmentInfo = firstAssignmentByName.get(name);
53
+ if (variableDeclaration.kind !== "const" &&
54
+ assignmentInfo &&
55
+ assignmentInfo.statementIndex < statementIndex) {
56
+ // Precedence rule: if a mutable binding is written via an earlier top-level
57
+ // assignment before a later redeclaration initializer, prefer the first write
58
+ // for stable naming. Example: `var a; a = 1; var a = 2;` hashes based on `1`,
59
+ // not `2`.
60
+ continue;
61
+ }
62
+ addRenameCandidate({
63
+ candidates,
64
+ hashToNames,
65
+ seenCandidateNames,
66
+ name,
67
+ hash,
68
+ kind: variableDeclaration.kind,
69
+ scope: programScope,
70
+ });
71
+ }
68
72
  }
69
73
  }
70
74
  else if (t.isFunctionDeclaration(effectiveStmt)) {
@@ -156,7 +160,8 @@ export const collectRenameCandidates = (options) => {
156
160
  }
157
161
  }
158
162
  }
159
- // Pass 2: Add the first write for any binding that hasn't already been fingerprinted.
163
+ // Pass 2: Add the first top-level write for any binding that hasn't already
164
+ // been fingerprinted.
160
165
  for (const [name, { rhs }] of firstAssignmentByName) {
161
166
  if (seenCandidateNames.has(name))
162
167
  continue;
@@ -0,0 +1,8 @@
1
+ import type { Scope } from "@babel/traverse";
2
+ import type { Node } from "@babel/types";
3
+ export declare const hasDisallowedFunctionClassAssignmentReference: (options: {
4
+ rhs: Node;
5
+ currentName: string;
6
+ programScope: Scope;
7
+ allowedReferenceIdentifierNames: ReadonlySet<string>;
8
+ }) => boolean;
@@ -0,0 +1,74 @@
1
+ import * as t from "@babel/types";
2
+ import { isStableRenamed } from "../../core/stable-naming.js";
3
+ const isNonReferenceIdentifier = (parent, parentKey) => {
4
+ if (!parent || !parentKey)
5
+ return false;
6
+ if (t.isMemberExpression(parent) || t.isOptionalMemberExpression(parent)) {
7
+ return parentKey === "property" && !parent.computed;
8
+ }
9
+ if (t.isObjectProperty(parent) ||
10
+ t.isObjectMethod(parent) ||
11
+ t.isClassMethod(parent) ||
12
+ t.isClassProperty(parent)) {
13
+ return parentKey === "key" && !parent.computed;
14
+ }
15
+ return false;
16
+ };
17
+ const collectReferencedIdentifierNames = (node, identifiers, parent, parentKey) => {
18
+ if (t.isIdentifier(node)) {
19
+ if (!isNonReferenceIdentifier(parent, parentKey)) {
20
+ identifiers.add(node.name);
21
+ }
22
+ return;
23
+ }
24
+ const keys = t.VISITOR_KEYS[node.type];
25
+ if (!keys)
26
+ return;
27
+ const nodeRecord = node;
28
+ for (const key of keys) {
29
+ const value = nodeRecord[key];
30
+ if (Array.isArray(value)) {
31
+ for (const item of value) {
32
+ if (item && typeof item === "object" && "type" in item) {
33
+ collectReferencedIdentifierNames(item, identifiers, node, key);
34
+ }
35
+ }
36
+ continue;
37
+ }
38
+ if (value && typeof value === "object" && "type" in value) {
39
+ collectReferencedIdentifierNames(value, identifiers, node, key);
40
+ }
41
+ }
42
+ };
43
+ const isProgramScopeConstBinding = (options) => {
44
+ const { programScope, identifierName } = options;
45
+ const referencedBinding = programScope.getBinding(identifierName);
46
+ return (referencedBinding?.scope === programScope &&
47
+ referencedBinding.kind === "const");
48
+ };
49
+ const isAllowedReferenceIdentifierName = (options) => {
50
+ const { currentName, identifierName, programScope, allowedReferenceIdentifierNames, } = options;
51
+ if (identifierName === currentName)
52
+ return true;
53
+ if (isStableRenamed(identifierName))
54
+ return true;
55
+ if (allowedReferenceIdentifierNames.has(identifierName))
56
+ return true;
57
+ return isProgramScopeConstBinding({ programScope, identifierName });
58
+ };
59
+ export const hasDisallowedFunctionClassAssignmentReference = (options) => {
60
+ const { rhs, currentName, programScope, allowedReferenceIdentifierNames } = options;
61
+ const referencedIdentifierNames = new Set();
62
+ collectReferencedIdentifierNames(rhs, referencedIdentifierNames);
63
+ for (const identifierName of referencedIdentifierNames) {
64
+ if (!isAllowedReferenceIdentifierName({
65
+ currentName,
66
+ identifierName,
67
+ programScope,
68
+ allowedReferenceIdentifierNames,
69
+ })) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ };
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 1,
4
- "notes": "Renames program-scope bindings to stable `$h_<hash>` names (simple identifiers only; destructuring patterns are skipped; uninitialized var/let bindings are stabilized only via direct top-level `x = ...` assignment statements; collisions are disambiguated with an encounter-order suffix). For classic scripts/UMD bundles, renaming globals can still be risky; see transform description and safety gates. Note: in scripts, sloppy-mode `this.foo` is treated conservatively as a possible global-object property lookup, and dynamic computed global-object lookups (including `this[expr]`) cause `var`/`function` renames to be skipped for the file. Default behavior is now unsafe with respect to dynamic-name hazards (e.g. direct eval): it will still run, which improves diff stability but may produce non-runnable output.",
2
+ "notes": "Renames program-scope bindings to stable `$h_<hash>` names (including destructuring-bound identifiers; uninitialized var/let bindings are stabilized via their first program-scope `x = ...` assignment. Assignments discovered inside function/class bodies are considered only when RHS identifier references stay in a stable allowlist: globals/known runtime names, already-stabilized names, self-reference, or program-scope const bindings; collisions are disambiguated with an encounter-order suffix). For classic scripts/UMD bundles, renaming globals can still be risky; see transform description and safety gates. Note: in scripts, sloppy-mode `this.foo` is treated conservatively as a possible global-object property lookup, and dynamic computed global-object lookups (including `this[expr]`) cause `var`/`function` renames to be skipped for the file. Default behavior is now unsafe with respect to dynamic-name hazards (e.g. direct eval): it will still run, which improves diff stability but may produce non-runnable output.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
- "diffSizePercent": 79.11224201300537,
8
- "evaluatedAt": "2026-02-07T21:20:02.357Z",
9
- "changedLines": 108399,
10
- "durationSeconds": 240.84553921,
11
- "stableNames": 14374
5
+ "diffSizePercent": 77.95101485429187,
6
+ "evaluatedAt": "2026-02-21T15:43:38.877Z",
7
+ "changedLines": 114776,
8
+ "durationSeconds": 43.780172209,
9
+ "stableNames": 15218
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 1
14
14
  }
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.116.0",
5
+ "version": "1.116.2",
6
6
  "description": "Transform minified JavaScript/TypeScript into a more readable form using deterministic AST-based transforms.",
7
7
  "repository": {
8
8
  "type": "git",
@@ -1,12 +0,0 @@
1
- import type { Scope } from "@babel/traverse";
2
- import type { Node, Program } from "@babel/types";
3
- type FirstProgramBodyAssignment = {
4
- rhs: Node;
5
- statementIndex: number;
6
- };
7
- export declare const collectFirstProgramBodyAssignments: (options: {
8
- program: Program;
9
- exportedNames: ReadonlySet<string>;
10
- programScope: Scope;
11
- }) => Map<string, FirstProgramBodyAssignment>;
12
- export {};
@@ -1,29 +0,0 @@
1
- import * as t from "@babel/types";
2
- import { isStableRenamed } from "../../core/stable-naming.js";
3
- export const collectFirstProgramBodyAssignments = (options) => {
4
- const { program, exportedNames, programScope } = options;
5
- const firstAssignmentByName = new Map();
6
- for (const [statementIndex, stmt] of program.body.entries()) {
7
- if (!t.isExpressionStatement(stmt))
8
- continue;
9
- const expression = stmt.expression;
10
- if (!t.isAssignmentExpression(expression))
11
- continue;
12
- if (expression.operator !== "=")
13
- continue;
14
- const left = expression.left;
15
- if (!t.isIdentifier(left))
16
- continue;
17
- const name = left.name;
18
- if (firstAssignmentByName.has(name))
19
- continue;
20
- if (isStableRenamed(name))
21
- continue;
22
- if (exportedNames.has(name))
23
- continue;
24
- if (!programScope.getBinding(name))
25
- continue;
26
- firstAssignmentByName.set(name, { rhs: expression.right, statementIndex });
27
- }
28
- return firstAssignmentByName;
29
- };