miniread 1.2.0 → 1.3.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.
- package/dist/transforms/expand-special-number-literals/expand-special-number-literals-transform.d.ts +2 -0
- package/dist/transforms/expand-special-number-literals/expand-special-number-literals-transform.js +139 -0
- package/dist/transforms/transform-registry.js +2 -0
- package/package.json +1 -1
- package/transform-manifest.json +10 -0
package/dist/transforms/expand-special-number-literals/expand-special-number-literals-transform.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
4
|
+
const traverse = require("@babel/traverse").default;
|
|
5
|
+
const t = require("@babel/types");
|
|
6
|
+
const directEvalCache = new WeakMap();
|
|
7
|
+
const containsDirectEval = (container) => {
|
|
8
|
+
const cached = directEvalCache.get(container.node);
|
|
9
|
+
if (cached !== undefined)
|
|
10
|
+
return cached;
|
|
11
|
+
let found = false;
|
|
12
|
+
container.traverse({
|
|
13
|
+
CallExpression(callPath) {
|
|
14
|
+
if (callPath.node.callee.type !== "Identifier")
|
|
15
|
+
return;
|
|
16
|
+
if (callPath.node.callee.name !== "eval")
|
|
17
|
+
return;
|
|
18
|
+
// We only care about direct eval in sloppy mode because it can introduce `var`
|
|
19
|
+
// bindings into the current function scope (which could shadow `Infinity`/`NaN`
|
|
20
|
+
// and change the meaning of an identifier replacement).
|
|
21
|
+
//
|
|
22
|
+
// Note: Even if `eval` is lexically shadowed, we can't reliably prove statically
|
|
23
|
+
// that it's not bound to the built-in eval at runtime (e.g. `const eval =
|
|
24
|
+
// globalThis.eval`). Be conservative and treat any `eval(...)` call as direct.
|
|
25
|
+
found = true;
|
|
26
|
+
callPath.stop();
|
|
27
|
+
},
|
|
28
|
+
Function(functionPath) {
|
|
29
|
+
if (functionPath.node === container.node)
|
|
30
|
+
return;
|
|
31
|
+
functionPath.skip();
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
directEvalCache.set(container.node, found);
|
|
35
|
+
return found;
|
|
36
|
+
};
|
|
37
|
+
const isWithinWithStatement = (path) => {
|
|
38
|
+
return Boolean(path.findParent((p) => p.isWithStatement()));
|
|
39
|
+
};
|
|
40
|
+
const isWithinSloppyDirectEvalScope = (path) => {
|
|
41
|
+
for (let functionPath = path.getFunctionParent(); functionPath; functionPath = functionPath.getFunctionParent()) {
|
|
42
|
+
if (functionPath.isInStrictMode())
|
|
43
|
+
continue;
|
|
44
|
+
if (containsDirectEval(functionPath))
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const programPath = path.scope.getProgramParent().path;
|
|
48
|
+
if (!programPath.isInStrictMode() && containsDirectEval(programPath)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
};
|
|
53
|
+
const isDivisionByZero = (value) => {
|
|
54
|
+
return value === 0 || Object.is(value, -0);
|
|
55
|
+
};
|
|
56
|
+
export const expandSpecialNumberLiteralsTransform = {
|
|
57
|
+
id: "expand-special-number-literals",
|
|
58
|
+
description: "Expands 1/0 to Infinity, -1/0 (and 1/-0) to -Infinity, and 0/0 to NaN (when not shadowed)",
|
|
59
|
+
scope: "file",
|
|
60
|
+
parallelizable: true,
|
|
61
|
+
transform(context) {
|
|
62
|
+
const { projectGraph } = context;
|
|
63
|
+
let nodesVisited = 0;
|
|
64
|
+
let transformationsApplied = 0;
|
|
65
|
+
for (const [, fileInfo] of projectGraph.files) {
|
|
66
|
+
traverse(fileInfo.ast, {
|
|
67
|
+
BinaryExpression(path) {
|
|
68
|
+
nodesVisited++;
|
|
69
|
+
const { operator, left } = path.node;
|
|
70
|
+
if (operator !== "/")
|
|
71
|
+
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")
|
|
75
|
+
return;
|
|
76
|
+
const leftPath = path.get("left");
|
|
77
|
+
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
|
+
const rightEvaluation = rightPath.evaluate();
|
|
89
|
+
if (!rightEvaluation.confident)
|
|
90
|
+
return;
|
|
91
|
+
if (typeof rightEvaluation.value !== "number")
|
|
92
|
+
return;
|
|
93
|
+
const rightValue = rightEvaluation.value;
|
|
94
|
+
if (!isDivisionByZero(rightValue))
|
|
95
|
+
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++;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return Promise.resolve({ nodesVisited, transformationsApplied });
|
|
138
|
+
},
|
|
139
|
+
};
|
|
@@ -2,6 +2,7 @@ import { expandBooleanLiteralsTransform } from "./expand-boolean-literals/expand
|
|
|
2
2
|
import { expandReturnSequenceTransform } from "./expand-return-sequence/expand-return-sequence-transform.js";
|
|
3
3
|
import { expandSequenceExpressionsTransform } from "./expand-sequence-expressions/expand-sequence-expressions-transform.js";
|
|
4
4
|
import { expandSequenceExpressionsV2Transform } from "./expand-sequence-expressions-v2/expand-sequence-expressions-v2-transform.js";
|
|
5
|
+
import { expandSpecialNumberLiteralsTransform } from "./expand-special-number-literals/expand-special-number-literals-transform.js";
|
|
5
6
|
import { expandSequenceExpressionsV3Transform } from "./expand-sequence-expressions-v3/expand-sequence-expressions-v3-transform.js";
|
|
6
7
|
import { expandThrowSequenceTransform } from "./expand-throw-sequence/expand-throw-sequence-transform.js";
|
|
7
8
|
import { expandUndefinedLiteralsTransform } from "./expand-undefined-literals/expand-undefined-literals-transform.js";
|
|
@@ -18,6 +19,7 @@ export const transformRegistry = {
|
|
|
18
19
|
[expandSequenceExpressionsTransform.id]: expandSequenceExpressionsTransform,
|
|
19
20
|
[expandSequenceExpressionsV2Transform.id]: expandSequenceExpressionsV2Transform,
|
|
20
21
|
[expandSequenceExpressionsV3Transform.id]: expandSequenceExpressionsV3Transform,
|
|
22
|
+
[expandSpecialNumberLiteralsTransform.id]: expandSpecialNumberLiteralsTransform,
|
|
21
23
|
[expandThrowSequenceTransform.id]: expandThrowSequenceTransform,
|
|
22
24
|
[expandUndefinedLiteralsTransform.id]: expandUndefinedLiteralsTransform,
|
|
23
25
|
[renameCatchParametersTransform.id]: renameCatchParametersTransform,
|
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
|
+
"version": "1.3.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",
|
package/transform-manifest.json
CHANGED
|
@@ -139,6 +139,16 @@
|
|
|
139
139
|
"recommended": true,
|
|
140
140
|
"evaluatedAt": "2026-01-22T21:39:53.578Z",
|
|
141
141
|
"notes": "Auto-added by evaluation script."
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "expand-special-number-literals",
|
|
145
|
+
"description": "Expands 1/0 to Infinity, -1/0 (and 1/-0) to -Infinity, and 0/0 to NaN (when not shadowed)",
|
|
146
|
+
"scope": "file",
|
|
147
|
+
"parallelizable": true,
|
|
148
|
+
"diffReductionImpact": 0,
|
|
149
|
+
"recommended": true,
|
|
150
|
+
"evaluatedAt": "2026-01-23T08:17:45.579Z",
|
|
151
|
+
"notes": "Auto-added by evaluation script."
|
|
142
152
|
}
|
|
143
153
|
],
|
|
144
154
|
"presetStats": {
|