miniread 1.56.0 → 1.57.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.
@@ -307,6 +307,10 @@ const manifestData = {
307
307
  recommended: true,
308
308
  notes: "Improves readability by using object shorthand for stable-renamed identifiers; no diff reduction impact expected.",
309
309
  evaluations: { "legacy:evaluation": { "evaluatedAt": "2026-01-24T12:08:16.000Z", "changedLines": 0, "durationSeconds": 0, "stableNames": 0, "diffSizePercent": 100 }, "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-01-25T23:42:52.520Z", "changedLines": 0, "durationSeconds": 44.329058792000005, "stableNames": 1357 } },
310
+ },
311
+ "use-optional-chaining": {
312
+ recommended: true,
313
+ evaluations: { "claude-code-2.1.10:claude-code-2.1.11": { "diffSizePercent": 100, "evaluatedAt": "2026-02-01T22:32:15.450Z", "changedLines": 0, "durationSeconds": 146.893061276, "stableNames": 1357 } },
310
314
  }
311
315
  };
312
316
  export const manifestEntries = Object.entries(transformRegistry)
@@ -372,6 +376,7 @@ export const recommendedTransformIds = [
372
376
  "simplify-boolean-negations",
373
377
  "simplify-string-trim",
374
378
  "split-variable-declarations",
379
+ "use-optional-chaining",
375
380
  "use-object-property-shorthand",
376
381
  "use-object-shorthand",
377
382
  "replace-dynamic-require-eval"
@@ -61,6 +61,7 @@ import { splitVariableDeclarationsTransform } from "../split-variable-declaratio
61
61
  import { stabilizeTopLevelBindingsTransform } from "../stabilize-top-level-bindings/stabilize-top-level-bindings-transform.js";
62
62
  import { useObjectPropertyShorthandTransform } from "../use-object-property-shorthand/use-object-property-shorthand-transform.js";
63
63
  import { useObjectShorthandTransform } from "../use-object-shorthand/use-object-shorthand-transform.js";
64
+ import { useOptionalChainingTransform } from "../use-optional-chaining/use-optional-chaining-transform.js";
64
65
  export const transformRegistry = {
65
66
  [expandBooleanLiteralsTransform.id]: expandBooleanLiteralsTransform,
66
67
  [expandSequenceExpressionsV4Transform.id]: expandSequenceExpressionsV4Transform,
@@ -123,5 +124,6 @@ export const transformRegistry = {
123
124
  [stabilizeTopLevelBindingsTransform.id]: stabilizeTopLevelBindingsTransform,
124
125
  [useObjectPropertyShorthandTransform.id]: useObjectPropertyShorthandTransform,
125
126
  [useObjectShorthandTransform.id]: useObjectShorthandTransform,
127
+ [useOptionalChainingTransform.id]: useOptionalChainingTransform,
126
128
  };
127
129
  export const allTransformIds = Object.keys(transformRegistry);
@@ -4,4 +4,4 @@
4
4
  * This lets us tune transform interactions intentionally instead of relying on
5
5
  * alphabetical ID sorting.
6
6
  */
7
- export declare const recommendedTransformOrder: readonly ["stabilize-top-level-bindings", "expand-boolean-literals", "expand-sequence-expressions-v5", "expand-special-number-literals", "expand-typeof-undefined-comparisons", "expand-undefined-literals", "remove-redundant-else", "rename-arguments-length-flags", "rename-asap-wrappers", "rename-awaiter-parameters", "rename-buffer-variables", "rename-catch-parameters", "rename-char-code-at", "rename-charcode-variables-v2", "rename-client-aliases", "rename-comparison-flags", "rename-date-now-start-times", "rename-default-options-parameters", "rename-deferred-resolve-parameters", "rename-destructured-aliases", "rename-error-first-callback-parameters", "rename-error-variables", "rename-event-parameters", "rename-interval-ids", "rename-loop-index-variables-v3", "rename-loop-length-variables", "rename-object-keys-variables", "rename-parameters-to-match-properties-v2", "rename-promise-executor-parameters-v2", "rename-range-parameters", "rename-read-file-lines", "rename-regex-builders", "rename-rest-parameters", "rename-search-parameters-variables", "rename-string-split-variables", "rename-this-aliases", "rename-timeout-ids", "rename-typeof-variables", "rename-typescript-helper-aliases", "rename-uint8array-concat-variables", "rename-url-parameters", "rename-url-variables", "rename-use-reference-guards-v2", "rename-use-reference-sets", "simplify-boolean-negations", "simplify-string-trim", "split-variable-declarations", "use-object-property-shorthand", "use-object-shorthand", "replace-dynamic-require-eval"];
7
+ export declare const recommendedTransformOrder: readonly ["stabilize-top-level-bindings", "expand-boolean-literals", "expand-sequence-expressions-v5", "expand-special-number-literals", "expand-typeof-undefined-comparisons", "expand-undefined-literals", "remove-redundant-else", "rename-arguments-length-flags", "rename-asap-wrappers", "rename-awaiter-parameters", "rename-buffer-variables", "rename-catch-parameters", "rename-char-code-at", "rename-charcode-variables-v2", "rename-client-aliases", "rename-comparison-flags", "rename-date-now-start-times", "rename-default-options-parameters", "rename-deferred-resolve-parameters", "rename-destructured-aliases", "rename-error-first-callback-parameters", "rename-error-variables", "rename-event-parameters", "rename-interval-ids", "rename-loop-index-variables-v3", "rename-loop-length-variables", "rename-object-keys-variables", "rename-parameters-to-match-properties-v2", "rename-promise-executor-parameters-v2", "rename-range-parameters", "rename-read-file-lines", "rename-regex-builders", "rename-rest-parameters", "rename-search-parameters-variables", "rename-string-split-variables", "rename-this-aliases", "rename-timeout-ids", "rename-typeof-variables", "rename-typescript-helper-aliases", "rename-uint8array-concat-variables", "rename-url-parameters", "rename-url-variables", "rename-use-reference-guards-v2", "rename-use-reference-sets", "simplify-boolean-negations", "simplify-string-trim", "split-variable-declarations", "use-optional-chaining", "use-object-property-shorthand", "use-object-shorthand", "replace-dynamic-require-eval"];
@@ -54,6 +54,7 @@ export const recommendedTransformOrder = [
54
54
  "simplify-boolean-negations",
55
55
  "simplify-string-trim",
56
56
  "split-variable-declarations",
57
+ "use-optional-chaining",
57
58
  "use-object-property-shorthand",
58
59
  "use-object-shorthand",
59
60
  "replace-dynamic-require-eval",
@@ -0,0 +1,2 @@
1
+ import type { Expression, Identifier } from "@babel/types";
2
+ export declare const buildOptionalAccess: (target: Identifier, alternate: Expression) => Expression | undefined;
@@ -0,0 +1,51 @@
1
+ import { cloneNode, isCallExpression, isIdentifier, isMemberExpression, isPrivateName, optionalCallExpression, optionalMemberExpression, } from "@babel/types";
2
+ export const buildOptionalAccess = (target, alternate) => {
3
+ const buildOptionalMemberChain = (member) => {
4
+ if (!isMemberExpression(member))
5
+ return;
6
+ const property = member.property;
7
+ if (isPrivateName(property))
8
+ return;
9
+ const object = member.object;
10
+ if (isIdentifier(object)) {
11
+ if (object.name !== target.name)
12
+ return;
13
+ return optionalMemberExpression(cloneNode(object), cloneNode(property), member.computed, true);
14
+ }
15
+ const optionalObject = buildOptionalMemberChain(object);
16
+ if (!optionalObject)
17
+ return;
18
+ return optionalMemberExpression(optionalObject, cloneNode(property), member.computed, false);
19
+ };
20
+ if (isMemberExpression(alternate)) {
21
+ return buildOptionalMemberChain(alternate);
22
+ }
23
+ if (!isCallExpression(alternate))
24
+ return;
25
+ if (isMemberExpression(alternate.callee)) {
26
+ const optionalCallee = buildOptionalMemberChain(alternate.callee);
27
+ if (!optionalCallee)
28
+ return;
29
+ const optionalCall = optionalCallExpression(optionalCallee, alternate.arguments.map((argument) => cloneNode(argument)), false);
30
+ if (alternate.typeParameters) {
31
+ optionalCall.typeParameters = cloneNode(alternate.typeParameters);
32
+ }
33
+ if ("typeArguments" in alternate && alternate.typeArguments) {
34
+ optionalCall.typeArguments = cloneNode(alternate.typeArguments);
35
+ }
36
+ return optionalCall;
37
+ }
38
+ if (isIdentifier(alternate.callee)) {
39
+ if (alternate.callee.name !== target.name)
40
+ return;
41
+ const optionalCall = optionalCallExpression(cloneNode(alternate.callee), alternate.arguments.map((argument) => cloneNode(argument)), true);
42
+ if (alternate.typeParameters) {
43
+ optionalCall.typeParameters = cloneNode(alternate.typeParameters);
44
+ }
45
+ if ("typeArguments" in alternate && alternate.typeArguments) {
46
+ optionalCall.typeArguments = cloneNode(alternate.typeArguments);
47
+ }
48
+ return optionalCall;
49
+ }
50
+ return undefined;
51
+ };
@@ -0,0 +1,2 @@
1
+ import type { Expression } from "@babel/types";
2
+ export declare const isNullishCheck: (test: Expression, identifierName: string) => boolean;
@@ -0,0 +1,61 @@
1
+ import { isBinaryExpression, isIdentifier, isLogicalExpression, isNullLiteral, isPrivateName, } from "@babel/types";
2
+ import { isUndefinedExpression } from "./is-undefined-expression.js";
3
+ import { unwrapParentheses } from "./unwrap-parentheses.js";
4
+ const noCoverage = {
5
+ coversNull: false,
6
+ coversUndefined: false,
7
+ };
8
+ const mergeCoverage = (left, right) => {
9
+ return {
10
+ coversNull: left.coversNull || right.coversNull,
11
+ coversUndefined: left.coversUndefined || right.coversUndefined,
12
+ };
13
+ };
14
+ const getNullishCoverage = (test, identifierName) => {
15
+ const unwrapped = unwrapParentheses(test);
16
+ if (isBinaryExpression(unwrapped)) {
17
+ if (unwrapped.operator !== "===")
18
+ return noCoverage;
19
+ const getValueCoverage = (value) => {
20
+ if (isPrivateName(value))
21
+ return noCoverage;
22
+ const unwrappedValue = unwrapParentheses(value);
23
+ if (isNullLiteral(unwrappedValue)) {
24
+ return { coversNull: true, coversUndefined: false };
25
+ }
26
+ if (isUndefinedExpression(unwrappedValue)) {
27
+ return { coversNull: false, coversUndefined: true };
28
+ }
29
+ return noCoverage;
30
+ };
31
+ const left = unwrapped.left;
32
+ const right = unwrapped.right;
33
+ if (isPrivateName(left))
34
+ return noCoverage;
35
+ if (isIdentifier(left) && left.name === identifierName) {
36
+ return getValueCoverage(right);
37
+ }
38
+ if (!isPrivateName(right) &&
39
+ isIdentifier(right) &&
40
+ right.name === identifierName) {
41
+ return getValueCoverage(left);
42
+ }
43
+ return noCoverage;
44
+ }
45
+ if (isLogicalExpression(unwrapped) && unwrapped.operator === "||") {
46
+ const leftCoverage = getNullishCoverage(unwrapped.left, identifierName);
47
+ if (!leftCoverage.coversNull && !leftCoverage.coversUndefined) {
48
+ return noCoverage;
49
+ }
50
+ const rightCoverage = getNullishCoverage(unwrapped.right, identifierName);
51
+ if (!rightCoverage.coversNull && !rightCoverage.coversUndefined) {
52
+ return noCoverage;
53
+ }
54
+ return mergeCoverage(leftCoverage, rightCoverage);
55
+ }
56
+ return noCoverage;
57
+ };
58
+ export const isNullishCheck = (test, identifierName) => {
59
+ const coverage = getNullishCoverage(test, identifierName);
60
+ return coverage.coversNull && coverage.coversUndefined;
61
+ };
@@ -0,0 +1,2 @@
1
+ import type { Expression } from "@babel/types";
2
+ export declare const isUndefinedExpression: (expression: Expression) => boolean;
@@ -0,0 +1,12 @@
1
+ import { isIdentifier, isNumericLiteral, isUnaryExpression, } from "@babel/types";
2
+ import { unwrapParentheses } from "./unwrap-parentheses.js";
3
+ export const isUndefinedExpression = (expression) => {
4
+ const unwrapped = unwrapParentheses(expression);
5
+ if (isIdentifier(unwrapped) && unwrapped.name === "undefined")
6
+ return true;
7
+ if (isUnaryExpression(unwrapped) && unwrapped.operator === "void") {
8
+ const argument = unwrapParentheses(unwrapped.argument);
9
+ return isNumericLiteral(argument);
10
+ }
11
+ return false;
12
+ };
@@ -0,0 +1,12 @@
1
+ {
2
+ "recommended": true,
3
+ "evaluations": {
4
+ "claude-code-2.1.10:claude-code-2.1.11": {
5
+ "diffSizePercent": 100,
6
+ "evaluatedAt": "2026-02-01T22:32:15.450Z",
7
+ "changedLines": 0,
8
+ "durationSeconds": 146.893061276,
9
+ "stableNames": 1357
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,2 @@
1
+ import type { Expression } from "@babel/types";
2
+ export declare const unwrapParentheses: (expression: Expression) => Expression;
@@ -0,0 +1,8 @@
1
+ import { isParenthesizedExpression } from "@babel/types";
2
+ export const unwrapParentheses = (expression) => {
3
+ let unwrapped = expression;
4
+ while (isParenthesizedExpression(unwrapped)) {
5
+ unwrapped = unwrapped.expression;
6
+ }
7
+ return unwrapped;
8
+ };
@@ -0,0 +1,2 @@
1
+ import { type Transform } from "../../core/types.js";
2
+ export declare const useOptionalChainingTransform: Transform;
@@ -0,0 +1,70 @@
1
+ import { createRequire } from "node:module";
2
+ import { isCallExpression, isIdentifier, isMemberExpression, } from "@babel/types";
3
+ import { getFilesToProcess, } from "../../core/types.js";
4
+ import { buildOptionalAccess } from "./build-optional-access.js";
5
+ import { isNullishCheck } from "./is-nullish-check.js";
6
+ import { isUndefinedExpression } from "./is-undefined-expression.js";
7
+ const require = createRequire(import.meta.url);
8
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
9
+ const traverse = require("@babel/traverse").default;
10
+ const shouldSkipUndefinedBinding = (path) => Boolean(path.scope.getBinding("undefined"));
11
+ export const useOptionalChainingTransform = {
12
+ id: "use-optional-chaining",
13
+ description: "Replace explicit null/undefined ternaries with optional chaining",
14
+ scope: "file",
15
+ parallelizable: true,
16
+ transform(context) {
17
+ let nodesVisited = 0;
18
+ let transformationsApplied = 0;
19
+ for (const fileInfo of getFilesToProcess(context)) {
20
+ traverse(fileInfo.ast, {
21
+ ConditionalExpression(path) {
22
+ nodesVisited++;
23
+ if (shouldSkipUndefinedBinding(path))
24
+ return;
25
+ const alternate = path.node.alternate;
26
+ const target = (() => {
27
+ if (isMemberExpression(alternate)) {
28
+ let object = alternate.object;
29
+ while (isMemberExpression(object)) {
30
+ object = object.object;
31
+ }
32
+ if (!isIdentifier(object))
33
+ return;
34
+ return object;
35
+ }
36
+ if (!isCallExpression(alternate))
37
+ return;
38
+ const callee = alternate.callee;
39
+ if (isIdentifier(callee))
40
+ return callee;
41
+ if (isMemberExpression(callee)) {
42
+ let object = callee.object;
43
+ while (isMemberExpression(object)) {
44
+ object = object.object;
45
+ }
46
+ if (!isIdentifier(object))
47
+ return;
48
+ return object;
49
+ }
50
+ return;
51
+ })();
52
+ if (!target)
53
+ return;
54
+ if (!path.scope.getBinding(target.name))
55
+ return;
56
+ if (!isNullishCheck(path.node.test, target.name))
57
+ return;
58
+ if (!isUndefinedExpression(path.node.consequent))
59
+ return;
60
+ const replacement = buildOptionalAccess(target, path.node.alternate);
61
+ if (!replacement)
62
+ return;
63
+ path.replaceWith(replacement);
64
+ transformationsApplied++;
65
+ },
66
+ });
67
+ }
68
+ return Promise.resolve({ nodesVisited, transformationsApplied });
69
+ },
70
+ };
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.56.0",
5
+ "version": "1.57.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",