miniread 1.116.2 → 1.117.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 (25) hide show
  1. package/dist/transforms-by-id/expand-special-number-literals/expand-special-number-literals-transform.js +23 -57
  2. package/dist/transforms-by-id/expand-special-number-literals/get-special-number-replacement.d.ts +22 -0
  3. package/dist/transforms-by-id/expand-special-number-literals/get-special-number-replacement.js +66 -0
  4. package/dist/transforms-by-id/expand-special-number-literals/manifest.json +6 -6
  5. package/dist/transforms-by-id/rename-charcode-variables-v2/manifest.json +7 -7
  6. package/dist/transforms-by-id/rename-charcode-variables-v2/rename-charcode-variables-v2-transform.js +13 -0
  7. package/dist/transforms-by-id/rename-file-extension-variables/manifest.json +6 -6
  8. package/dist/transforms-by-id/rename-file-extension-variables/match-file-extension-expression.d.ts +2 -0
  9. package/dist/transforms-by-id/rename-file-extension-variables/match-file-extension-expression.js +145 -0
  10. package/dist/transforms-by-id/rename-file-extension-variables/rename-file-extension-variables-transform.js +11 -92
  11. package/dist/transforms-by-id/rename-parameters-to-match-properties-v2/manifest.json +7 -7
  12. package/dist/transforms-by-id/rename-parameters-to-match-properties-v2/rename-parameters-to-match-properties-v2-transform.js +23 -5
  13. package/dist/transforms-by-id/rename-regex-builders/manifest.json +7 -7
  14. package/dist/transforms-by-id/rename-regex-builders/rename-regex-builders-transform.js +53 -4
  15. package/dist/transforms-by-id/rename-this-aliases/manifest.json +7 -7
  16. package/dist/transforms-by-id/rename-this-aliases/rename-this-aliases-transform.js +0 -2
  17. package/dist/transforms-by-id/rename-to-buffer-results/manifest.json +7 -7
  18. package/dist/transforms-by-id/rename-to-buffer-results/rename-to-buffer-results-transform.js +65 -27
  19. package/dist/transforms-by-id/rename-use-reference-guards-v2/get-reference-usage.js +6 -2
  20. package/dist/transforms-by-id/rename-use-reference-guards-v2/is-use-reference-false-initializer.js +2 -0
  21. package/dist/transforms-by-id/rename-use-reference-guards-v2/manifest.json +7 -7
  22. package/dist/transforms-by-id/rename-use-reference-guards-v2/rename-use-reference-guards-v2-transform.js +6 -3
  23. package/dist/transforms-by-id/use-optional-chaining/is-nullish-check.js +19 -1
  24. package/dist/transforms-by-id/use-optional-chaining/manifest.json +6 -6
  25. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { getFilesToProcess } from "../../core/types.js";
3
+ import { getSpecialNumberReplacement } from "./get-special-number-replacement.js";
3
4
  const require = createRequire(import.meta.url);
4
5
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5
6
  const traverse = require("@babel/traverse").default;
@@ -20,9 +21,6 @@ const containsDirectEval = (container) => {
20
21
  // bindings into the current function scope (which could shadow `Infinity`/`NaN`
21
22
  // and change the meaning of an identifier replacement).
22
23
  //
23
- // Note: Even if `eval` is lexically shadowed, we can't reliably prove statically
24
- // that it's not bound to the built-in eval at runtime (e.g. `const eval =
25
- // globalThis.eval`). Be conservative and treat any `eval(...)` call as direct.
26
24
  found = true;
27
25
  callPath.stop();
28
26
  },
@@ -66,71 +64,39 @@ export const expandSpecialNumberLiteralsTransform = {
66
64
  traverse(fileInfo.ast, {
67
65
  BinaryExpression(path) {
68
66
  nodesVisited++;
69
- const { operator, left } = path.node;
70
- if (operator !== "/")
67
+ if (path.node.operator !== "/")
71
68
  return;
72
- // Babel types allow `PrivateName` as a BinaryExpression operand for `#x in obj`
73
- // brand checks (operator `in`). This guard is just to satisfy the type system.
74
- if (left.type === "PrivateName")
69
+ if (path.node.left.type === "PrivateName")
75
70
  return;
76
71
  const leftPath = path.get("left");
77
72
  const rightPath = path.get("right");
78
- // Avoid dropping side effects: `evaluate()` can be confident for expressions that
79
- // are not pure (e.g. `(foo(), 1) / 0`). Only rewrite when both sides are pure.
80
- if (!leftPath.isPure() || !rightPath.isPure())
81
- return;
82
- const leftEvaluation = leftPath.evaluate();
83
- if (!leftEvaluation.confident)
84
- return;
85
- if (typeof leftEvaluation.value !== "number")
86
- return;
87
- const leftValue = leftEvaluation.value;
88
73
  const rightEvaluation = rightPath.evaluate();
89
74
  if (!rightEvaluation.confident)
90
75
  return;
91
76
  if (typeof rightEvaluation.value !== "number")
92
77
  return;
93
- const rightValue = rightEvaluation.value;
94
- if (!isDivisionByZero(rightValue))
78
+ if (!isDivisionByZero(rightEvaluation.value))
95
79
  return;
96
- const result = leftValue / rightValue;
97
- if (Number.isNaN(result)) {
98
- // `0/0` is unaffected by dynamic scope, but `NaN` can be shadowed via `with` and
99
- // sloppy direct `eval()` in this scope or any enclosing scope.
100
- if (isWithinWithStatement(path))
101
- return;
102
- if (isWithinSloppyDirectEvalScope(path))
103
- return;
104
- if (path.scope.hasBinding("NaN", true))
105
- return;
106
- path.replaceWith(t.identifier("NaN"));
107
- transformationsApplied++;
108
- return;
109
- }
110
- if (result === Infinity) {
111
- // `1/0` is unaffected by dynamic scope, but `Infinity` can be shadowed via `with` and
112
- // sloppy direct `eval()` in this scope or any enclosing scope.
113
- if (isWithinWithStatement(path))
114
- return;
115
- if (isWithinSloppyDirectEvalScope(path))
116
- return;
117
- if (path.scope.hasBinding("Infinity", true))
118
- return;
119
- path.replaceWith(t.identifier("Infinity"));
120
- transformationsApplied++;
121
- return;
122
- }
123
- if (result === -Infinity) {
124
- if (isWithinWithStatement(path))
125
- return;
126
- if (isWithinSloppyDirectEvalScope(path))
127
- return;
128
- if (path.scope.hasBinding("Infinity", true))
129
- return;
130
- path.replaceWith(t.unaryExpression("-", t.identifier("Infinity")));
131
- transformationsApplied++;
80
+ const leftEvaluation = leftPath.evaluate();
81
+ const leftValue = leftEvaluation.confident && typeof leftEvaluation.value === "number"
82
+ ? leftEvaluation.value
83
+ : undefined;
84
+ const replacement = getSpecialNumberReplacement({
85
+ left: path.node.left,
86
+ leftIsPure: leftPath.isPure(),
87
+ right: path.node.right,
88
+ rightIsPure: rightPath.isPure(),
89
+ rightValue: rightEvaluation.value,
90
+ leftValue,
91
+ path,
92
+ t,
93
+ isWithinWithStatement,
94
+ isWithinSloppyDirectEvalScope,
95
+ });
96
+ if (!replacement)
132
97
  return;
133
- }
98
+ path.replaceWith(replacement);
99
+ transformationsApplied++;
134
100
  },
135
101
  });
136
102
  }
@@ -0,0 +1,22 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { BinaryExpression, Expression } from "@babel/types";
3
+ type BabelTypesApi = {
4
+ identifier: (name: string) => Expression;
5
+ unaryExpression: (operator: "-", argument: Expression) => Expression;
6
+ sequenceExpression: (expressions: Expression[]) => Expression;
7
+ binaryExpression: (operator: "*", left: Expression, right: Expression) => Expression;
8
+ };
9
+ type GetSpecialNumberReplacementArguments = {
10
+ left: Expression;
11
+ leftIsPure: boolean;
12
+ right: Expression;
13
+ rightIsPure: boolean;
14
+ rightValue: number;
15
+ leftValue: number | undefined;
16
+ path: NodePath<BinaryExpression>;
17
+ t: BabelTypesApi;
18
+ isWithinWithStatement: (path: NodePath) => boolean;
19
+ isWithinSloppyDirectEvalScope: (path: NodePath) => boolean;
20
+ };
21
+ export declare const getSpecialNumberReplacement: (arguments_: GetSpecialNumberReplacementArguments) => Expression | undefined;
22
+ export {};
@@ -0,0 +1,66 @@
1
+ const hasUnsafeInfinityScope = (path, isWithinWithStatement, isWithinSloppyDirectEvalScope) => {
2
+ if (isWithinWithStatement(path))
3
+ return true;
4
+ if (isWithinSloppyDirectEvalScope(path))
5
+ return true;
6
+ return path.scope.hasBinding("Infinity", true);
7
+ };
8
+ const hasUnsafeNaNScope = (path, isWithinWithStatement, isWithinSloppyDirectEvalScope) => {
9
+ if (isWithinWithStatement(path))
10
+ return true;
11
+ if (isWithinSloppyDirectEvalScope(path))
12
+ return true;
13
+ return path.scope.hasBinding("NaN", true);
14
+ };
15
+ const createInfinityProduct = (arguments_) => {
16
+ if (Object.is(arguments_.rightValue, -0)) {
17
+ return arguments_.t.binaryExpression("*", arguments_.left, arguments_.t.unaryExpression("-", arguments_.t.identifier("Infinity")));
18
+ }
19
+ return arguments_.t.binaryExpression("*", arguments_.left, arguments_.t.identifier("Infinity"));
20
+ };
21
+ const withSideEffectsPreserved = (arguments_, replacement) => {
22
+ if (arguments_.leftIsPure && arguments_.rightIsPure)
23
+ return replacement;
24
+ const expressions = [];
25
+ if (!arguments_.leftIsPure)
26
+ expressions.push(arguments_.left);
27
+ if (!arguments_.rightIsPure)
28
+ expressions.push(arguments_.right);
29
+ expressions.push(replacement);
30
+ return arguments_.t.sequenceExpression(expressions);
31
+ };
32
+ export const getSpecialNumberReplacement = (arguments_) => {
33
+ if (arguments_.leftValue === undefined) {
34
+ if (!arguments_.leftIsPure)
35
+ return undefined;
36
+ // Replacement still evaluates `left`; skip when `right` has side effects so
37
+ // we do not reorder runtime evaluation from `left`-then-`right` to
38
+ // `right`-then-`left`.
39
+ if (!arguments_.rightIsPure)
40
+ return undefined;
41
+ if (hasUnsafeInfinityScope(arguments_.path, arguments_.isWithinWithStatement, arguments_.isWithinSloppyDirectEvalScope)) {
42
+ return undefined;
43
+ }
44
+ return withSideEffectsPreserved(arguments_, createInfinityProduct(arguments_));
45
+ }
46
+ const result = arguments_.leftValue / arguments_.rightValue;
47
+ if (Number.isNaN(result)) {
48
+ if (hasUnsafeNaNScope(arguments_.path, arguments_.isWithinWithStatement, arguments_.isWithinSloppyDirectEvalScope)) {
49
+ return undefined;
50
+ }
51
+ return withSideEffectsPreserved(arguments_, arguments_.t.identifier("NaN"));
52
+ }
53
+ if (result === Infinity) {
54
+ if (hasUnsafeInfinityScope(arguments_.path, arguments_.isWithinWithStatement, arguments_.isWithinSloppyDirectEvalScope)) {
55
+ return undefined;
56
+ }
57
+ return withSideEffectsPreserved(arguments_, arguments_.t.identifier("Infinity"));
58
+ }
59
+ if (result === -Infinity) {
60
+ if (hasUnsafeInfinityScope(arguments_.path, arguments_.isWithinWithStatement, arguments_.isWithinSloppyDirectEvalScope)) {
61
+ return undefined;
62
+ }
63
+ return withSideEffectsPreserved(arguments_, arguments_.t.unaryExpression("-", arguments_.t.identifier("Infinity")));
64
+ }
65
+ return withSideEffectsPreserved(arguments_, createInfinityProduct(arguments_));
66
+ };
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Auto-added by evaluation script.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
5
  "diffSizePercent": 100,
8
- "evaluatedAt": "2026-02-07T21:16:49.085Z",
9
- "changedLines": 310,
10
- "durationSeconds": 206.054788542,
6
+ "evaluatedAt": "2026-02-21T16:47:56.238Z",
7
+ "changedLines": 314,
8
+ "durationSeconds": 40.9949115,
11
9
  "stableNames": 1357
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Derives names from charCodeAt argument for better stability (e.g., $charCodeAtIndex, $charCodeAtIndexPlus1).",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
5
  "diffSizePercent": 100,
8
- "evaluatedAt": "2026-02-07T21:19:25.440Z",
9
- "changedLines": 238,
10
- "durationSeconds": 150.12261538,
11
- "stableNames": 1367
6
+ "evaluatedAt": "2026-02-21T15:11:23.165Z",
7
+ "changedLines": 292,
8
+ "durationSeconds": 192.198072639,
9
+ "stableNames": 1370
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -55,6 +55,19 @@ const getArgumentSuffix = (expression, depth = 0) => {
55
55
  }
56
56
  }
57
57
  }
58
+ // Member expression: charCodeAt(cursor.pos) → "Pos"
59
+ if (expression.type === "MemberExpression") {
60
+ if (expression.property.type === "Identifier") {
61
+ return capitalize(expression.property.name);
62
+ }
63
+ if (expression.property.type === "NumericLiteral") {
64
+ if (Number.isInteger(expression.property.value) &&
65
+ expression.property.value >= 0) {
66
+ return String(expression.property.value);
67
+ }
68
+ return undefined;
69
+ }
70
+ }
58
71
  return undefined;
59
72
  };
60
73
  /**
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Measured with baseline none: 100.00%. Added to recommended for readability.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
5
  "diffSizePercent": 100,
8
- "evaluatedAt": "2026-02-07T21:35:34.320Z",
9
- "changedLines": 4,
10
- "durationSeconds": 315.94628195100006,
6
+ "evaluatedAt": "2026-02-21T17:06:33.862Z",
7
+ "changedLines": 40,
8
+ "durationSeconds": 27.991182833,
11
9
  "stableNames": 1358
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -0,0 +1,2 @@
1
+ import type { Expression } from "@babel/types";
2
+ export declare const isFileExtensionExpression: (expression: Expression) => boolean;
@@ -0,0 +1,145 @@
1
+ import * as t from "@babel/types";
2
+ const unwrapExpressionWithStringFallback = (expression) => {
3
+ if (!t.isLogicalExpression(expression))
4
+ return undefined;
5
+ if (expression.operator !== "||" && expression.operator !== "??") {
6
+ return undefined;
7
+ }
8
+ if (!t.isStringLiteral(expression.right))
9
+ return undefined;
10
+ return expression.left;
11
+ };
12
+ const getMemberPropertyName = (callee) => {
13
+ if (t.isIdentifier(callee.property))
14
+ return callee.property.name;
15
+ if (callee.computed && t.isStringLiteral(callee.property)) {
16
+ return callee.property.value;
17
+ }
18
+ return undefined;
19
+ };
20
+ const getCallTarget = (expression, propertyName, argumentsLength) => {
21
+ if (t.isCallExpression(expression)) {
22
+ if (expression.arguments.length !== argumentsLength)
23
+ return undefined;
24
+ if (!t.isMemberExpression(expression.callee))
25
+ return undefined;
26
+ const property = getMemberPropertyName(expression.callee);
27
+ if (property !== propertyName)
28
+ return undefined;
29
+ if (t.isSuper(expression.callee.object))
30
+ return undefined;
31
+ return expression.callee.object;
32
+ }
33
+ if (t.isOptionalCallExpression(expression)) {
34
+ if (expression.arguments.length !== argumentsLength)
35
+ return undefined;
36
+ if (!t.isOptionalMemberExpression(expression.callee))
37
+ return undefined;
38
+ const property = getMemberPropertyName(expression.callee);
39
+ if (property !== propertyName)
40
+ return undefined;
41
+ if (t.isSuper(expression.callee.object))
42
+ return undefined;
43
+ return expression.callee.object;
44
+ }
45
+ return undefined;
46
+ };
47
+ const isPathExtnameCall = (expression) => {
48
+ if (!t.isCallExpression(expression) &&
49
+ !t.isOptionalCallExpression(expression)) {
50
+ return false;
51
+ }
52
+ if (expression.arguments.length !== 1)
53
+ return false;
54
+ const callee = expression.callee;
55
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee)) {
56
+ return false;
57
+ }
58
+ const property = getMemberPropertyName(callee);
59
+ return property === "extname";
60
+ };
61
+ const isSplitDotCall = (expression) => {
62
+ if (!t.isCallExpression(expression) &&
63
+ !t.isOptionalCallExpression(expression)) {
64
+ return false;
65
+ }
66
+ const splitTarget = getCallTarget(expression, "split", 1);
67
+ if (!splitTarget)
68
+ return false;
69
+ const [delimiter] = expression.arguments;
70
+ if (!delimiter || !t.isStringLiteral(delimiter))
71
+ return false;
72
+ return delimiter.value === ".";
73
+ };
74
+ const getSliceOneTarget = (expression) => {
75
+ const sliceTarget = getCallTarget(expression, "slice", 1);
76
+ if (!sliceTarget)
77
+ return undefined;
78
+ if (!t.isCallExpression(expression) &&
79
+ !t.isOptionalCallExpression(expression)) {
80
+ return undefined;
81
+ }
82
+ const [sliceStart] = expression.arguments;
83
+ if (!sliceStart || !t.isExpression(sliceStart))
84
+ return undefined;
85
+ if (!t.isNumericLiteral(sliceStart))
86
+ return undefined;
87
+ if (sliceStart.value !== 1)
88
+ return undefined;
89
+ return sliceTarget;
90
+ };
91
+ const isNegativeOne = (expression) => {
92
+ return t.isNumericLiteral(expression)
93
+ ? expression.value === -1
94
+ : t.isUnaryExpression(expression) &&
95
+ expression.operator === "-" &&
96
+ t.isNumericLiteral(expression.argument) &&
97
+ expression.argument.value === 1;
98
+ };
99
+ const isSplitAtLastExpression = (expression) => {
100
+ const atTarget = getCallTarget(expression, "at", 1);
101
+ if (!atTarget)
102
+ return false;
103
+ if (!t.isCallExpression(expression) &&
104
+ !t.isOptionalCallExpression(expression)) {
105
+ return false;
106
+ }
107
+ const [atIndex] = expression.arguments;
108
+ if (!atIndex || !t.isExpression(atIndex))
109
+ return false;
110
+ if (!isNegativeOne(atIndex))
111
+ return false;
112
+ return isSplitDotCall(atTarget);
113
+ };
114
+ const isPathExtnameDerivedExpression = (expression) => {
115
+ if (isPathExtnameCall(expression))
116
+ return true;
117
+ const lowerTarget = getCallTarget(expression, "toLowerCase", 0);
118
+ if (lowerTarget && isPathExtnameCall(lowerTarget))
119
+ return true;
120
+ const sliceTarget = getSliceOneTarget(expression);
121
+ if (sliceTarget && isPathExtnameCall(sliceTarget))
122
+ return true;
123
+ if (sliceTarget) {
124
+ const lowerBeforeSlice = getCallTarget(sliceTarget, "toLowerCase", 0);
125
+ if (lowerBeforeSlice && isPathExtnameCall(lowerBeforeSlice))
126
+ return true;
127
+ }
128
+ if (lowerTarget) {
129
+ const sliceBeforeLower = getSliceOneTarget(lowerTarget);
130
+ if (sliceBeforeLower && isPathExtnameCall(sliceBeforeLower))
131
+ return true;
132
+ }
133
+ return false;
134
+ };
135
+ export const isFileExtensionExpression = (expression) => {
136
+ const normalized = unwrapExpressionWithStringFallback(expression) ?? expression;
137
+ if (isPathExtnameDerivedExpression(normalized))
138
+ return true;
139
+ const lowerTarget = getCallTarget(normalized, "toLowerCase", 0);
140
+ const extractionTarget = lowerTarget ?? normalized;
141
+ const popTarget = getCallTarget(extractionTarget, "pop", 0);
142
+ if (popTarget)
143
+ return isSplitDotCall(popTarget);
144
+ return isSplitAtLastExpression(extractionTarget);
145
+ };
@@ -2,74 +2,18 @@ import { createRequire } from "node:module";
2
2
  import * as t from "@babel/types";
3
3
  import { RenameGroup, isStableRenamed } from "../../core/stable-naming.js";
4
4
  import { getFilesToProcess } from "../../core/types.js";
5
+ import { isFileExtensionExpression } from "./match-file-extension-expression.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
7
8
  const traverse = require("@babel/traverse").default;
8
- const unwrapExpressionWithStringFallback = (expression) => {
9
- if (!t.isLogicalExpression(expression))
10
- return undefined;
11
- if (expression.operator !== "||" && expression.operator !== "??") {
12
- return undefined;
13
- }
14
- if (!t.isStringLiteral(expression.right))
15
- return undefined;
16
- return expression.left;
17
- };
18
- const getMemberPropertyName = (callee) => {
19
- if (t.isIdentifier(callee.property))
20
- return callee.property.name;
21
- if (callee.computed && t.isStringLiteral(callee.property)) {
22
- return callee.property.value;
23
- }
24
- return undefined;
25
- };
26
- const getCallTarget = (expression, propertyName, argumentsLength) => {
27
- if (t.isCallExpression(expression)) {
28
- if (expression.arguments.length !== argumentsLength)
29
- return undefined;
30
- if (!t.isMemberExpression(expression.callee))
31
- return undefined;
32
- const property = getMemberPropertyName(expression.callee);
33
- if (property !== propertyName)
34
- return undefined;
35
- if (t.isSuper(expression.callee.object))
36
- return undefined;
37
- return expression.callee.object;
38
- }
39
- if (t.isOptionalCallExpression(expression)) {
40
- if (expression.arguments.length !== argumentsLength)
41
- return undefined;
42
- if (!t.isOptionalMemberExpression(expression.callee))
43
- return undefined;
44
- const property = getMemberPropertyName(expression.callee);
45
- if (property !== propertyName)
46
- return undefined;
47
- if (t.isSuper(expression.callee.object))
48
- return undefined;
49
- return expression.callee.object;
50
- }
51
- return undefined;
52
- };
53
- const isFileExtensionExpression = (expression) => {
54
- const normalized = unwrapExpressionWithStringFallback(expression);
55
- if (!normalized)
56
- return false;
57
- const lowerTarget = getCallTarget(normalized, "toLowerCase", 0);
58
- const popTarget = lowerTarget ?? normalized;
59
- const splitTarget = getCallTarget(popTarget, "pop", 0);
60
- if (!splitTarget)
61
- return false;
62
- const splitCallTarget = getCallTarget(splitTarget, "split", 1);
63
- if (!splitCallTarget)
64
- return false;
65
- if (!t.isCallExpression(splitTarget) &&
66
- !t.isOptionalCallExpression(splitTarget)) {
67
- return false;
68
- }
69
- const [delimiter] = splitTarget.arguments;
70
- if (!delimiter || !t.isStringLiteral(delimiter))
71
- return false;
72
- return delimiter.value === ".";
9
+ const addRenameCandidate = (currentName, scope, group) => {
10
+ if (isStableRenamed(currentName))
11
+ return;
12
+ group.add({
13
+ scope,
14
+ currentName,
15
+ baseName: "fileExtension",
16
+ });
73
17
  };
74
18
  const collectFileExtensionParameterRenames = (path, group) => {
75
19
  for (const parameter of path.node.params) {
@@ -79,19 +23,7 @@ const collectFileExtensionParameterRenames = (path, group) => {
79
23
  continue;
80
24
  if (!isFileExtensionExpression(parameter.right))
81
25
  continue;
82
- const currentName = parameter.left.name;
83
- if (isStableRenamed(currentName))
84
- continue;
85
- if (currentName === "fileExtension")
86
- continue;
87
- const binding = path.scope.getBinding(currentName);
88
- if (!binding?.constant)
89
- continue;
90
- group.add({
91
- scope: path.scope,
92
- currentName,
93
- baseName: "fileExtension",
94
- });
26
+ addRenameCandidate(parameter.left.name, path.scope, group);
95
27
  }
96
28
  };
97
29
  export const renameFileExtensionVariablesTransform = {
@@ -126,20 +58,7 @@ export const renameFileExtensionVariablesTransform = {
126
58
  return;
127
59
  if (!isFileExtensionExpression(init))
128
60
  return;
129
- const currentName = id.name;
130
- if (isStableRenamed(currentName))
131
- return;
132
- if (currentName === "fileExtension")
133
- return;
134
- const binding = path.scope.getBinding(currentName);
135
- if (!binding?.constant)
136
- return;
137
- const scope = path.scope;
138
- group.add({
139
- scope,
140
- currentName,
141
- baseName: "fileExtension",
142
- });
61
+ addRenameCandidate(id.name, path.scope, group);
143
62
  },
144
63
  });
145
64
  transformationsApplied += group.apply();
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Extends v1 to also handle this.property = param assignments.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
- "diffSizePercent": 99.99057581754784,
8
- "evaluatedAt": "2026-02-07T21:11:38.249Z",
9
- "changedLines": 20501,
10
- "durationSeconds": 241.015244141,
5
+ "diffSizePercent": 99.99244056393393,
6
+ "evaluatedAt": "2026-02-21T16:50:11.730Z",
7
+ "changedLines": 20517,
8
+ "durationSeconds": 41.078400416,
11
9
  "stableNames": 1357
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -5,6 +5,25 @@ import { isStableRenamed } from "../../core/stable-naming.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;
8
+ const hasUnresolvedGlobalReference = (functionPath, name) => {
9
+ let found = false;
10
+ functionPath.traverse({
11
+ Identifier(identifierPath) {
12
+ if (found) {
13
+ identifierPath.skip();
14
+ return;
15
+ }
16
+ if (identifierPath.node.name !== name)
17
+ return;
18
+ if (!identifierPath.isReferencedIdentifier())
19
+ return;
20
+ if (identifierPath.scope.hasBinding(name))
21
+ return;
22
+ found = true;
23
+ },
24
+ });
25
+ return found;
26
+ };
8
27
  export const renameParametersToMatchPropertiesV2Transform = {
9
28
  id: "rename-parameters-to-match-properties-v2",
10
29
  description: "Renames parameters assigned to object properties or this.property",
@@ -77,11 +96,6 @@ export const renameParametersToMatchPropertiesV2Transform = {
77
96
  }
78
97
  }
79
98
  if (candidateName) {
80
- // Check global references (conservative: if file uses 'console', don't use 'console')
81
- const programScope = path.scope.getProgramParent();
82
- if (Object.hasOwn(programScope.globals, candidateName)) {
83
- isConsistent = false;
84
- }
85
99
  // Check for nested scope shadowing
86
100
  // If we rename `a` -> `b`, and a nested scope has `b` bound,
87
101
  // and we use `a` in that nested scope, it will become `b` (referring to inner `b`).
@@ -90,6 +104,10 @@ export const renameParametersToMatchPropertiesV2Transform = {
90
104
  if (wouldBeShadowed) {
91
105
  isConsistent = false;
92
106
  }
107
+ if (isConsistent &&
108
+ hasUnresolvedGlobalReference(path, candidateName)) {
109
+ isConsistent = false;
110
+ }
93
111
  }
94
112
  // Constraints:
95
113
  // 1. New name must be valid identifier
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "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
5
  "diffSizePercent": 100,
8
- "evaluatedAt": "2026-02-07T21:20:54.287Z",
9
- "changedLines": 6,
10
- "durationSeconds": 193.29200997400002,
11
- "stableNames": 1358
6
+ "evaluatedAt": "2026-02-21T16:55:47.330Z",
7
+ "changedLines": 66,
8
+ "durationSeconds": 33.044639417,
9
+ "stableNames": 1359
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -6,10 +6,24 @@ import { isRegexBuilderObject } from "./is-regex-builder-object.js";
6
6
  const require = createRequire(import.meta.url);
7
7
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
8
8
  const traverse = require("@babel/traverse").default;
9
- const BASE_NAME = "regexBuilder";
9
+ const BUILDER_BASE_NAME = "regexBuilder";
10
+ const FACTORY_BASE_NAME = "createRegexBuilder";
11
+ const getReturnedObjectBindingName = (functionPath) => {
12
+ const bodyPath = functionPath.get("body");
13
+ const statements = bodyPath.get("body");
14
+ for (const statementPath of statements) {
15
+ if (!statementPath.isReturnStatement())
16
+ continue;
17
+ const argumentPath = statementPath.get("argument");
18
+ if (!argumentPath.isIdentifier())
19
+ continue;
20
+ return argumentPath.node.name;
21
+ }
22
+ return undefined;
23
+ };
10
24
  export const renameRegexBuildersTransform = {
11
25
  id: "rename-regex-builders",
12
- description: "Renames fluent regex builder objects to $regexBuilder/$regexBuilder2 when they expose replace() and getRegex().",
26
+ description: "Renames fluent regex builder objects and their factory functions to stable names.",
13
27
  scope: "file",
14
28
  parallelizable: true,
15
29
  transform(context) {
@@ -35,12 +49,47 @@ export const renameRegexBuildersTransform = {
35
49
  return;
36
50
  if (!isRegexBuilderObject(initPath, id.name, binding))
37
51
  return;
38
- if (path.scope.hasBinding("RegExp", true))
52
+ group.add({
53
+ scope: path.scope,
54
+ currentName: id.name,
55
+ baseName: BUILDER_BASE_NAME,
56
+ });
57
+ },
58
+ FunctionDeclaration(path) {
59
+ nodesVisited++;
60
+ const id = path.node.id;
61
+ if (!id)
62
+ return;
63
+ if (isStableRenamed(id.name))
64
+ return;
65
+ const functionBinding = path.scope.getBinding(id.name);
66
+ if (!functionBinding)
67
+ return;
68
+ const returnedBindingName = getReturnedObjectBindingName(path);
69
+ if (!returnedBindingName)
70
+ return;
71
+ const returnedBinding = path.scope.getBinding(returnedBindingName);
72
+ if (!returnedBinding)
73
+ return;
74
+ const functionScope = path.get("body").scope;
75
+ if (returnedBinding.scope.getFunctionParent() !== functionScope) {
76
+ return;
77
+ }
78
+ if (!returnedBinding.constant)
79
+ return;
80
+ const bindingPath = returnedBinding.path;
81
+ if (!bindingPath.isVariableDeclarator())
82
+ return;
83
+ const initPath = bindingPath.get("init");
84
+ if (!initPath.isObjectExpression())
85
+ return;
86
+ if (!isRegexBuilderObject(initPath, returnedBindingName, returnedBinding)) {
39
87
  return;
88
+ }
40
89
  group.add({
41
90
  scope: path.scope,
42
91
  currentName: id.name,
43
- baseName: BASE_NAME,
92
+ baseName: FACTORY_BASE_NAME,
44
93
  });
45
94
  },
46
95
  });
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Measured with baseline none: 0.00%. Improves readability by stabilizing `this` aliases used for closures.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
- "diffSizePercent": 99.99623032701913,
8
- "evaluatedAt": "2026-02-07T21:13:14.820Z",
9
- "changedLines": 1382,
10
- "durationSeconds": 159.934804951,
5
+ "diffSizePercent": 99.99622028196697,
6
+ "evaluatedAt": "2026-02-21T16:46:34.305Z",
7
+ "changedLines": 1729,
8
+ "durationSeconds": 44.609521334,
11
9
  "stableNames": 1358
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -31,8 +31,6 @@ export const renameThisAliasesTransform = {
31
31
  const binding = path.scope.getBinding(currentName);
32
32
  if (!binding)
33
33
  return;
34
- if (!binding.constant)
35
- return;
36
34
  if (binding.scope.block.type === "Program" &&
37
35
  exportedNames.has(currentName)) {
38
36
  return;
@@ -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
- "diffSizePercent": 100,
7
- "evaluatedAt": "2026-02-07T21:21:20.792Z",
8
- "changedLines": 56,
9
- "durationSeconds": 161.838725924,
4
+ "diffSizePercent": 99.98299126885134,
5
+ "evaluatedAt": "2026-02-21T16:54:10.786Z",
6
+ "changedLines": 392,
7
+ "durationSeconds": 50.692648416000004,
10
8
  "stableNames": 1359
11
9
  }
12
- }
10
+ },
11
+ "recommended": true,
12
+ "recommendedOrder": 100
13
13
  }
@@ -4,6 +4,17 @@ 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 isBufferFromCall = (callee) => {
8
+ if (callee.type !== "MemberExpression") {
9
+ return false;
10
+ }
11
+ if (callee.computed || callee.object.type !== "Identifier") {
12
+ return false;
13
+ }
14
+ return (callee.object.name === "Buffer" &&
15
+ callee.property.type === "Identifier" &&
16
+ callee.property.name === "from");
17
+ };
7
18
  const isToBufferCall = (callee) => {
8
19
  if (callee.type !== "MemberExpression" &&
9
20
  callee.type !== "OptionalMemberExpression") {
@@ -23,9 +34,51 @@ const isToBufferCall = (callee) => {
23
34
  }
24
35
  return (callee.property.type === "Identifier" && callee.property.name === "toBuffer");
25
36
  };
37
+ const isBufferProducingCall = (callExpression) => {
38
+ return (isToBufferCall(callExpression.callee) ||
39
+ (callExpression.type === "CallExpression" &&
40
+ isBufferFromCall(callExpression.callee)));
41
+ };
42
+ const getBufferProducingCallExpression = (expression) => {
43
+ if (expression.type === "CallExpression" ||
44
+ expression.type === "OptionalCallExpression") {
45
+ return isBufferProducingCall(expression) ? expression : undefined;
46
+ }
47
+ if (expression.type !== "AwaitExpression") {
48
+ return undefined;
49
+ }
50
+ if (expression.argument.type !== "CallExpression" &&
51
+ expression.argument.type !== "OptionalCallExpression") {
52
+ return undefined;
53
+ }
54
+ return isBufferProducingCall(expression.argument)
55
+ ? expression.argument
56
+ : undefined;
57
+ };
58
+ const queueBufferRename = (path, renameGroup, queuedBindings, currentName) => {
59
+ if (isStableRenamed(currentName)) {
60
+ return;
61
+ }
62
+ if (currentName.toLowerCase().includes("buffer")) {
63
+ return;
64
+ }
65
+ const binding = path.scope.getBinding(currentName);
66
+ if (!binding) {
67
+ return;
68
+ }
69
+ if (queuedBindings.has(binding)) {
70
+ return;
71
+ }
72
+ queuedBindings.add(binding);
73
+ renameGroup.add({
74
+ scope: path.scope,
75
+ currentName,
76
+ baseName: "buffer",
77
+ });
78
+ };
26
79
  export const renameToBufferResultsTransform = {
27
80
  id: "rename-to-buffer-results",
28
- description: "Renames variables assigned from toBuffer() results to buffer",
81
+ description: "Renames variables assigned from toBuffer()/Buffer.from() results to buffer",
29
82
  scope: "file",
30
83
  parallelizable: true,
31
84
  transform(context) {
@@ -33,6 +86,7 @@ export const renameToBufferResultsTransform = {
33
86
  let transformationsApplied = 0;
34
87
  for (const fileInfo of getFilesToProcess(context)) {
35
88
  const renameGroup = new RenameGroup();
89
+ const queuedBindings = new Set();
36
90
  traverse(fileInfo.ast, {
37
91
  VariableDeclarator(path) {
38
92
  nodesVisited++;
@@ -40,40 +94,24 @@ export const renameToBufferResultsTransform = {
40
94
  if (!init || id.type !== "Identifier") {
41
95
  return;
42
96
  }
43
- let callExpression;
44
- if (init.type === "CallExpression" ||
45
- init.type === "OptionalCallExpression") {
46
- callExpression = init;
47
- }
48
- else if (init.type === "AwaitExpression") {
49
- if (init.argument.type !== "CallExpression" &&
50
- init.argument.type !== "OptionalCallExpression") {
51
- return;
52
- }
53
- callExpression = init.argument;
54
- }
55
- else {
56
- return;
57
- }
58
- if (!isToBufferCall(callExpression.callee)) {
97
+ if (!getBufferProducingCallExpression(init)) {
59
98
  return;
60
99
  }
61
- const currentName = id.name;
62
- if (isStableRenamed(currentName)) {
100
+ queueBufferRename(path, renameGroup, queuedBindings, id.name);
101
+ },
102
+ AssignmentExpression(path) {
103
+ nodesVisited++;
104
+ const { left, operator, right } = path.node;
105
+ if (operator !== "=") {
63
106
  return;
64
107
  }
65
- if (currentName.toLowerCase().includes("buffer")) {
108
+ if (left.type !== "Identifier") {
66
109
  return;
67
110
  }
68
- const binding = path.scope.getBinding(currentName);
69
- if (!binding?.constant) {
111
+ if (!getBufferProducingCallExpression(right)) {
70
112
  return;
71
113
  }
72
- renameGroup.add({
73
- scope: path.scope,
74
- currentName,
75
- baseName: "buffer",
76
- });
114
+ queueBufferRename(path, renameGroup, queuedBindings, left.name);
77
115
  },
78
116
  });
79
117
  transformationsApplied += renameGroup.apply();
@@ -2,6 +2,12 @@ import { getMemberExpressionForReference } from "./get-member-expression-for-ref
2
2
  const getKnownBooleanValue = (expression) => {
3
3
  if (expression.type === "BooleanLiteral")
4
4
  return expression.value;
5
+ if (expression.type === "NumericLiteral") {
6
+ if (expression.value === 0)
7
+ return false;
8
+ if (expression.value === 1)
9
+ return true;
10
+ }
5
11
  if (expression.type === "UnaryExpression" &&
6
12
  expression.operator === "!" &&
7
13
  expression.argument.type === "NumericLiteral" &&
@@ -33,8 +39,6 @@ export const getReferenceUsage = (binding) => {
33
39
  return { isSafe: false, hasRead, hasTrueWrite };
34
40
  const right = parentPath.node.right;
35
41
  const value = getKnownBooleanValue(right);
36
- if (value === undefined)
37
- return { isSafe: false, hasRead, hasTrueWrite };
38
42
  if (value) {
39
43
  hasTrueWrite = true;
40
44
  }
@@ -1,6 +1,8 @@
1
1
  const isKnownFalseValue = (expression) => {
2
2
  if (expression.type === "BooleanLiteral")
3
3
  return !expression.value;
4
+ if (expression.type === "NumericLiteral")
5
+ return expression.value === 0;
4
6
  if (expression.type === "UnaryExpression" &&
5
7
  expression.operator === "!" &&
6
8
  expression.argument.type === "NumericLiteral" &&
@@ -1,14 +1,14 @@
1
1
  {
2
- "recommended": true,
3
- "recommendedOrder": 100,
4
2
  "notes": "Supersedes rename-use-reference-guards and also covers guards that reset to false.",
5
3
  "evaluations": {
6
4
  "claude-code-2.1.10:claude-code-2.1.11": {
7
5
  "diffSizePercent": 100,
8
- "evaluatedAt": "2026-02-08T05:50:16.997Z",
9
- "changedLines": 166,
10
- "durationSeconds": 29.099741833,
11
- "stableNames": 1358
6
+ "evaluatedAt": "2026-02-21T16:41:17.818Z",
7
+ "changedLines": 202,
8
+ "durationSeconds": 34.530502708,
9
+ "stableNames": 1359
12
10
  }
13
- }
11
+ },
12
+ "recommended": true,
13
+ "recommendedOrder": 100
14
14
  }
@@ -7,9 +7,10 @@ const require = createRequire(import.meta.url);
7
7
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
8
8
  const traverse = require("@babel/traverse").default;
9
9
  const BASE_NAME = "hasRunRef";
10
+ const SAFE_READ_ONLY_BASE_NAME = "guardRef";
10
11
  const renameUseReferenceGuardsV2Transform = {
11
12
  id: "rename-use-reference-guards-v2",
12
- description: "Renames boolean useRef(false) guard variables (including reset-to-false guards) to $hasRunRef/$hasRunRef2/... or hasRunRef/hasRunRef2/...",
13
+ description: "Renames safe useRef(false) guard variables: true-write guards to $hasRunRef/$hasRunRef2/... and read-only/false-only guards to $guardRef/$guardRef2/...",
13
14
  scope: "file",
14
15
  parallelizable: true,
15
16
  transform(context) {
@@ -34,12 +35,14 @@ const renameUseReferenceGuardsV2Transform = {
34
35
  return;
35
36
  const usage = getReferenceUsage(binding);
36
37
  // Unlike v1, this version also accepts guards that reset to false
37
- if (!usage.isSafe || !usage.hasRead || !usage.hasTrueWrite)
38
+ if (!usage.isSafe)
39
+ return;
40
+ if (!usage.hasRead)
38
41
  return;
39
42
  group.add({
40
43
  scope: path.scope,
41
44
  currentName: id.name,
42
- baseName: BASE_NAME,
45
+ baseName: usage.hasTrueWrite ? BASE_NAME : SAFE_READ_ONLY_BASE_NAME,
43
46
  });
44
47
  },
45
48
  });
@@ -14,7 +14,8 @@ const mergeCoverage = (left, right) => {
14
14
  const getNullishCoverage = (test, identifierName) => {
15
15
  const unwrapped = unwrapParentheses(test);
16
16
  if (isBinaryExpression(unwrapped)) {
17
- if (unwrapped.operator !== "===")
17
+ const operator = unwrapped.operator;
18
+ if (operator !== "===" && operator !== "==")
18
19
  return noCoverage;
19
20
  const getValueCoverage = (value) => {
20
21
  if (isPrivateName(value))
@@ -32,6 +33,23 @@ const getNullishCoverage = (test, identifierName) => {
32
33
  const right = unwrapped.right;
33
34
  if (isPrivateName(left))
34
35
  return noCoverage;
36
+ if (operator === "==") {
37
+ if (isIdentifier(left) && left.name === identifierName) {
38
+ const valueCoverage = getValueCoverage(right);
39
+ if (valueCoverage.coversNull) {
40
+ return { coversNull: true, coversUndefined: true };
41
+ }
42
+ }
43
+ if (!isPrivateName(right) &&
44
+ isIdentifier(right) &&
45
+ right.name === identifierName) {
46
+ const valueCoverage = getValueCoverage(left);
47
+ if (valueCoverage.coversNull) {
48
+ return { coversNull: true, coversUndefined: true };
49
+ }
50
+ }
51
+ return noCoverage;
52
+ }
35
53
  if (isIdentifier(left) && left.name === identifierName) {
36
54
  return getValueCoverage(right);
37
55
  }
@@ -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:46:19.740Z",
8
- "changedLines": 582,
9
- "durationSeconds": 34.011375708,
5
+ "evaluatedAt": "2026-02-21T16:46:18.553Z",
6
+ "changedLines": 614,
7
+ "durationSeconds": 40.285438,
10
8
  "stableNames": 1357
11
9
  }
12
- }
10
+ },
11
+ "recommended": true,
12
+ "recommendedOrder": 100
13
13
  }
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.2",
5
+ "version": "1.117.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",