miniread 1.5.0 → 1.6.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.
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Identifier, MemberExpression, OptionalMemberExpression } from "@babel/types";
3
+ export declare const getMemberExpressionForReference: (referencePath: NodePath<Identifier>) => NodePath<MemberExpression | OptionalMemberExpression> | undefined;
@@ -0,0 +1,10 @@
1
+ export const getMemberExpressionForReference = (referencePath) => {
2
+ const parentPath = referencePath.parentPath;
3
+ if (parentPath.isMemberExpression() &&
4
+ parentPath.node.object === referencePath.node)
5
+ return parentPath;
6
+ if (parentPath.isOptionalMemberExpression() &&
7
+ parentPath.node.object === referencePath.node)
8
+ return parentPath;
9
+ return;
10
+ };
@@ -0,0 +1,8 @@
1
+ import type { Binding } from "@babel/traverse";
2
+ type ReferenceUsage = {
3
+ isSafe: boolean;
4
+ hasRead: boolean;
5
+ hasTrueWrite: boolean;
6
+ };
7
+ export declare const getReferenceUsage: (binding: Binding) => ReferenceUsage;
8
+ export {};
@@ -0,0 +1,58 @@
1
+ import { getMemberExpressionForReference } from "./get-member-expression-for-reference.js";
2
+ const getKnownBooleanValue = (expression) => {
3
+ if (expression.type === "BooleanLiteral")
4
+ return expression.value;
5
+ if (expression.type === "UnaryExpression" &&
6
+ expression.operator === "!" &&
7
+ expression.argument.type === "NumericLiteral" &&
8
+ (expression.argument.value === 0 || expression.argument.value === 1)) {
9
+ return !expression.argument.value;
10
+ }
11
+ return;
12
+ };
13
+ export const getReferenceUsage = (binding) => {
14
+ let hasRead = false;
15
+ let hasTrueWrite = false;
16
+ for (const referencePath of binding.referencePaths) {
17
+ if (!referencePath.isIdentifier())
18
+ return { isSafe: false, hasRead, hasTrueWrite };
19
+ const memberPath = getMemberExpressionForReference(referencePath);
20
+ if (!memberPath)
21
+ return { isSafe: false, hasRead, hasTrueWrite };
22
+ const memberNode = memberPath.node;
23
+ if (memberNode.computed)
24
+ return { isSafe: false, hasRead, hasTrueWrite };
25
+ if (memberNode.property.type !== "Identifier" ||
26
+ memberNode.property.name !== "current") {
27
+ return { isSafe: false, hasRead, hasTrueWrite };
28
+ }
29
+ const parentPath = memberPath.parentPath;
30
+ if (parentPath.isAssignmentExpression() &&
31
+ parentPath.node.left === memberNode) {
32
+ if (parentPath.node.operator !== "=")
33
+ return { isSafe: false, hasRead, hasTrueWrite };
34
+ const right = parentPath.node.right;
35
+ const value = getKnownBooleanValue(right);
36
+ if (value === undefined)
37
+ return { isSafe: false, hasRead, hasTrueWrite };
38
+ if (value) {
39
+ hasTrueWrite = true;
40
+ }
41
+ continue;
42
+ }
43
+ if (parentPath.isUpdateExpression() &&
44
+ parentPath.node.argument === memberNode) {
45
+ return { isSafe: false, hasRead, hasTrueWrite };
46
+ }
47
+ if (parentPath.isUnaryExpression() &&
48
+ parentPath.node.operator === "delete" &&
49
+ parentPath.node.argument === memberNode) {
50
+ return { isSafe: false, hasRead, hasTrueWrite };
51
+ }
52
+ if (!memberPath.isReferenced()) {
53
+ return { isSafe: false, hasRead, hasTrueWrite };
54
+ }
55
+ hasRead = true;
56
+ }
57
+ return { isSafe: true, hasRead, hasTrueWrite };
58
+ };
@@ -0,0 +1,2 @@
1
+ import type { Binding, Scope } from "@babel/traverse";
2
+ export declare const getTargetName: (bindingScope: Scope, binding: Binding) => string | undefined;
@@ -0,0 +1,23 @@
1
+ import { isValidBindingIdentifier } from "./is-valid-binding-identifier.js";
2
+ const BASE_NAME = "hasRunRef";
3
+ // Cap retries to avoid pathological loops while handling large bundles.
4
+ const MAX_CANDIDATES = 1000;
5
+ export const getTargetName = (bindingScope, binding) => {
6
+ const programScope = bindingScope.getProgramParent();
7
+ for (let index = 0; index < MAX_CANDIDATES; index++) {
8
+ const candidate = index === 0 ? BASE_NAME : `${BASE_NAME}${index + 1}`;
9
+ if (!isValidBindingIdentifier(candidate))
10
+ continue;
11
+ // Avoid shadowing bindings in parent scopes.
12
+ if (bindingScope.hasBinding(candidate))
13
+ continue;
14
+ if (Object.hasOwn(programScope.globals, candidate))
15
+ continue;
16
+ const wouldBeShadowed = binding.referencePaths.some((referencePath) => referencePath.scope !== bindingScope &&
17
+ referencePath.scope.hasBinding(candidate));
18
+ if (wouldBeShadowed)
19
+ continue;
20
+ return candidate;
21
+ }
22
+ return;
23
+ };
@@ -0,0 +1,2 @@
1
+ import type { VariableDeclarator } from "@babel/types";
2
+ export declare const isUseReferenceFalseInitializer: (declarator: VariableDeclarator) => boolean;
@@ -0,0 +1,40 @@
1
+ const isKnownFalseValue = (expression) => {
2
+ if (expression.type === "BooleanLiteral")
3
+ return !expression.value;
4
+ if (expression.type === "UnaryExpression" &&
5
+ expression.operator === "!" &&
6
+ expression.argument.type === "NumericLiteral" &&
7
+ expression.argument.value === 1) {
8
+ return true;
9
+ }
10
+ return false;
11
+ };
12
+ export const isUseReferenceFalseInitializer = (declarator) => {
13
+ const init = declarator.init;
14
+ if (!init)
15
+ return false;
16
+ if (init.type !== "CallExpression")
17
+ return false;
18
+ if (init.arguments.length !== 1)
19
+ return false;
20
+ const argument = init.arguments[0];
21
+ if (!argument)
22
+ return false;
23
+ if (argument.type === "SpreadElement")
24
+ return false;
25
+ if (argument.type === "ArgumentPlaceholder")
26
+ return false;
27
+ if (!isKnownFalseValue(argument))
28
+ return false;
29
+ const callee = init.callee;
30
+ if (callee.type === "Identifier") {
31
+ return callee.name === "useRef";
32
+ }
33
+ if (callee.type !== "MemberExpression")
34
+ return false;
35
+ if (callee.computed)
36
+ return false;
37
+ if (callee.property.type !== "Identifier")
38
+ return false;
39
+ return callee.property.name === "useRef";
40
+ };
@@ -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,3 @@
1
+ import type { Transform } from "../../core/types.js";
2
+ declare const renameUseReferenceGuardsV2Transform: Transform;
3
+ export { renameUseReferenceGuardsV2Transform };
@@ -0,0 +1,52 @@
1
+ import { createRequire } from "node:module";
2
+ import { getReferenceUsage } from "./get-reference-usage.js";
3
+ import { getTargetName } from "./get-target-name.js";
4
+ import { isUseReferenceFalseInitializer } from "./is-use-reference-false-initializer.js";
5
+ const require = createRequire(import.meta.url);
6
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
7
+ const traverse = require("@babel/traverse").default;
8
+ const renameUseReferenceGuardsV2Transform = {
9
+ id: "rename-use-reference-guards-v2",
10
+ description: "Renames boolean useRef(false) guard variables (including reset-to-false guards) to hasRunRef/hasRunRef2/...",
11
+ scope: "file",
12
+ parallelizable: true,
13
+ transform(context) {
14
+ const { projectGraph } = context;
15
+ let nodesVisited = 0;
16
+ let transformationsApplied = 0;
17
+ for (const [, fileInfo] of projectGraph.files) {
18
+ traverse(fileInfo.ast, {
19
+ VariableDeclarator(path) {
20
+ nodesVisited++;
21
+ const id = path.node.id;
22
+ if (id.type !== "Identifier")
23
+ return;
24
+ if (id.name.length > 2)
25
+ return;
26
+ if (!isUseReferenceFalseInitializer(path.node))
27
+ return;
28
+ const binding = path.scope.getBinding(id.name);
29
+ if (!binding)
30
+ return;
31
+ if (!binding.constant)
32
+ return;
33
+ const usage = getReferenceUsage(binding);
34
+ if (!usage.isSafe || !usage.hasRead || !usage.hasTrueWrite)
35
+ return;
36
+ const targetName = getTargetName(path.scope, binding);
37
+ if (!targetName)
38
+ return;
39
+ if (id.name === targetName)
40
+ return;
41
+ path.scope.rename(id.name, targetName);
42
+ transformationsApplied++;
43
+ },
44
+ });
45
+ }
46
+ return Promise.resolve({
47
+ nodesVisited,
48
+ transformationsApplied,
49
+ });
50
+ },
51
+ };
52
+ export { renameUseReferenceGuardsV2Transform };
@@ -14,6 +14,7 @@ import { renameLoopIndexVariablesTransform } from "./rename-loop-index-variables
14
14
  import { renamePromiseExecutorParametersTransform } from "./rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js";
15
15
  import { renameTimeoutIdsTransform } from "./rename-timeout-ids/rename-timeout-ids-transform.js";
16
16
  import { renameUseReferenceGuardsTransform } from "./rename-use-reference-guards/rename-use-reference-guards-transform.js";
17
+ import { renameUseReferenceGuardsV2Transform } from "./rename-use-reference-guards-v2/rename-use-reference-guards-v2-transform.js";
17
18
  import { splitVariableDeclarationsTransform } from "./split-variable-declarations/split-variable-declarations-transform.js";
18
19
  export const transformRegistry = {
19
20
  [expandBooleanLiteralsTransform.id]: expandBooleanLiteralsTransform,
@@ -32,6 +33,7 @@ export const transformRegistry = {
32
33
  [renamePromiseExecutorParametersTransform.id]: renamePromiseExecutorParametersTransform,
33
34
  [renameTimeoutIdsTransform.id]: renameTimeoutIdsTransform,
34
35
  [renameUseReferenceGuardsTransform.id]: renameUseReferenceGuardsTransform,
36
+ [renameUseReferenceGuardsV2Transform.id]: renameUseReferenceGuardsV2Transform,
35
37
  [splitVariableDeclarationsTransform.id]: splitVariableDeclarationsTransform,
36
38
  };
37
39
  export const allTransformIds = Object.keys(transformRegistry);
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.5.0",
5
+ "version": "1.6.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",
@@ -115,9 +115,19 @@
115
115
  "scope": "file",
116
116
  "parallelizable": true,
117
117
  "diffReductionImpact": 0,
118
- "recommended": true,
118
+ "recommended": false,
119
119
  "evaluatedAt": "2026-01-22T17:03:19.826Z",
120
- "notes": "Auto-added by evaluation script."
120
+ "notes": "Superseded by rename-use-reference-guards-v2.",
121
+ "supersededBy": "rename-use-reference-guards-v2"
122
+ },
123
+ {
124
+ "id": "rename-use-reference-guards-v2",
125
+ "description": "Renames boolean useRef(false) guard variables (including reset-to-false guards) to hasRunRef/hasRunRef2/...",
126
+ "scope": "file",
127
+ "parallelizable": true,
128
+ "diffReductionImpact": 0,
129
+ "recommended": true,
130
+ "notes": "Supersedes rename-use-reference-guards and also covers guards that reset to false."
121
131
  },
122
132
  {
123
133
  "id": "rename-timeout-ids",