miniread 1.43.0 → 1.45.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 (31) hide show
  1. package/dist/transforms/_generated/manifest.js +14 -0
  2. package/dist/transforms/_generated/registry.js +4 -0
  3. package/dist/transforms/rename-range-parameters/get-first-non-directive-statement.d.ts +2 -0
  4. package/dist/transforms/rename-range-parameters/get-first-non-directive-statement.js +16 -0
  5. package/dist/transforms/rename-range-parameters/get-range-parameter-default-assignments.d.ts +4 -0
  6. package/dist/transforms/rename-range-parameters/get-range-parameter-default-assignments.js +57 -0
  7. package/dist/transforms/rename-range-parameters/get-range-parameter-names.d.ts +8 -0
  8. package/dist/transforms/rename-range-parameters/get-range-parameter-names.js +54 -0
  9. package/dist/transforms/rename-range-parameters/has-count-non-positive-guard.d.ts +3 -0
  10. package/dist/transforms/rename-range-parameters/has-count-non-positive-guard.js +49 -0
  11. package/dist/transforms/rename-range-parameters/has-start-count-sum.d.ts +3 -0
  12. package/dist/transforms/rename-range-parameters/has-start-count-sum.js +55 -0
  13. package/dist/transforms/rename-range-parameters/manifest.json +7 -0
  14. package/dist/transforms/rename-range-parameters/rename-range-parameters-transform.d.ts +2 -0
  15. package/dist/transforms/rename-range-parameters/rename-range-parameters-transform.js +50 -0
  16. package/dist/transforms/rename-safe-property-accessor-parameters/get-return-expression.d.ts +3 -0
  17. package/dist/transforms/rename-safe-property-accessor-parameters/get-return-expression.js +21 -0
  18. package/dist/transforms/rename-safe-property-accessor-parameters/get-returned-function.d.ts +3 -0
  19. package/dist/transforms/rename-safe-property-accessor-parameters/get-returned-function.js +22 -0
  20. package/dist/transforms/rename-safe-property-accessor-parameters/is-member-access.d.ts +2 -0
  21. package/dist/transforms/rename-safe-property-accessor-parameters/is-member-access.js +24 -0
  22. package/dist/transforms/rename-safe-property-accessor-parameters/is-nullish-check.d.ts +4 -0
  23. package/dist/transforms/rename-safe-property-accessor-parameters/is-nullish-check.js +74 -0
  24. package/dist/transforms/rename-safe-property-accessor-parameters/manifest.json +7 -0
  25. package/dist/transforms/rename-safe-property-accessor-parameters/match-safe-property-accessor-factory.d.ts +9 -0
  26. package/dist/transforms/rename-safe-property-accessor-parameters/match-safe-property-accessor-factory.js +44 -0
  27. package/dist/transforms/rename-safe-property-accessor-parameters/rename-safe-property-accessor-parameters-transform.d.ts +3 -0
  28. package/dist/transforms/rename-safe-property-accessor-parameters/rename-safe-property-accessor-parameters-transform.js +56 -0
  29. package/dist/transforms/rename-safe-property-accessor-parameters/unwrap-parentheses.d.ts +2 -0
  30. package/dist/transforms/rename-safe-property-accessor-parameters/unwrap-parentheses.js +7 -0
  31. package/package.json +1 -1
@@ -216,6 +216,13 @@ const manifestData = {
216
216
  notes: "Auto-added by evaluation script.",
217
217
  changedLines: 970,
218
218
  },
219
+ "rename-range-parameters": {
220
+ diffReductionImpact: 0,
221
+ recommended: true,
222
+ evaluatedAt: "2026-01-25T09:40:26.000Z",
223
+ notes: "Renames range-style parameters when the count defaults to the start value and start resets to 0.",
224
+ changedLines: 0,
225
+ },
219
226
  "rename-regex-builders": {
220
227
  diffReductionImpact: 0,
221
228
  recommended: true,
@@ -236,6 +243,13 @@ const manifestData = {
236
243
  evaluatedAt: "2026-01-25T20:11:03.532Z",
237
244
  changedLines: 1300,
238
245
  },
246
+ "rename-safe-property-accessor-parameters": {
247
+ diffReductionImpact: -0.000018848364904400228,
248
+ recommended: false,
249
+ evaluatedAt: "2026-01-25T10:04:22.417Z",
250
+ notes: "Negative diff reduction in evaluation; keep optional.",
251
+ changedLines: 0,
252
+ },
239
253
  "rename-this-aliases": {
240
254
  diffReductionImpact: 0.00003779718033036783,
241
255
  recommended: true,
@@ -30,9 +30,11 @@ import { renameParametersToMatchPropertiesTransform } from "../rename-parameters
30
30
  import { renameParametersToMatchPropertiesV2Transform } from "../rename-parameters-to-match-properties-v2/rename-parameters-to-match-properties-v2-transform.js";
31
31
  import { renameProcessStdoutHandlersTransform } from "../rename-process-stdout-handlers/rename-process-stdout-handlers-transform.js";
32
32
  import { renamePromiseExecutorParametersTransform } from "../rename-promise-executor-parameters/rename-promise-executor-parameters-transform.js";
33
+ import { renameRangeParametersTransform } from "../rename-range-parameters/rename-range-parameters-transform.js";
33
34
  import { renameRegexBuildersTransform } from "../rename-regex-builders/rename-regex-builders-transform.js";
34
35
  import { renameReplaceChildParametersTransform } from "../rename-replace-child-parameters/rename-replace-child-parameters-transform.js";
35
36
  import { renameRestParametersTransform } from "../rename-rest-parameters/rename-rest-parameters-transform.js";
37
+ import { renameSafePropertyAccessorParametersTransform } from "../rename-safe-property-accessor-parameters/rename-safe-property-accessor-parameters-transform.js";
36
38
  import { renameThisAliasesTransform } from "../rename-this-aliases/rename-this-aliases-transform.js";
37
39
  import { renameTimeoutDurationParametersTransform } from "../rename-timeout-duration-parameters/rename-timeout-duration-parameters-transform.js";
38
40
  import { renameTimeoutIdsTransform } from "../rename-timeout-ids/rename-timeout-ids-transform.js";
@@ -79,9 +81,11 @@ export const transformRegistry = {
79
81
  [renameParametersToMatchPropertiesV2Transform.id]: renameParametersToMatchPropertiesV2Transform,
80
82
  [renameProcessStdoutHandlersTransform.id]: renameProcessStdoutHandlersTransform,
81
83
  [renamePromiseExecutorParametersTransform.id]: renamePromiseExecutorParametersTransform,
84
+ [renameRangeParametersTransform.id]: renameRangeParametersTransform,
82
85
  [renameRegexBuildersTransform.id]: renameRegexBuildersTransform,
83
86
  [renameReplaceChildParametersTransform.id]: renameReplaceChildParametersTransform,
84
87
  [renameRestParametersTransform.id]: renameRestParametersTransform,
88
+ [renameSafePropertyAccessorParametersTransform.id]: renameSafePropertyAccessorParametersTransform,
85
89
  [renameThisAliasesTransform.id]: renameThisAliasesTransform,
86
90
  [renameTimeoutDurationParametersTransform.id]: renameTimeoutDurationParametersTransform,
87
91
  [renameTimeoutIdsTransform.id]: renameTimeoutIdsTransform,
@@ -0,0 +1,2 @@
1
+ import * as t from "@babel/types";
2
+ export declare const getFirstNonDirectiveStatement: (body: t.BlockStatement) => t.Statement | undefined;
@@ -0,0 +1,16 @@
1
+ import * as t from "@babel/types";
2
+ const isDirectivePrologueStatement = (statement) => {
3
+ if (!t.isExpressionStatement(statement))
4
+ return false;
5
+ return t.isStringLiteral(statement.expression);
6
+ };
7
+ export const getFirstNonDirectiveStatement = (body) => {
8
+ for (const statement of body.body) {
9
+ if (t.isEmptyStatement(statement))
10
+ continue;
11
+ if (isDirectivePrologueStatement(statement))
12
+ continue;
13
+ return statement;
14
+ }
15
+ return undefined;
16
+ };
@@ -0,0 +1,4 @@
1
+ import * as t from "@babel/types";
2
+ type AssignmentPair = [t.AssignmentExpression, t.AssignmentExpression];
3
+ export declare const getRangeParameterDefaultAssignments: (consequent: t.Statement) => AssignmentPair | undefined;
4
+ export {};
@@ -0,0 +1,57 @@
1
+ import * as t from "@babel/types";
2
+ const getSequenceAssignments = (expression) => {
3
+ if (!t.isSequenceExpression(expression))
4
+ return undefined;
5
+ if (expression.expressions.length !== 2)
6
+ return undefined;
7
+ const firstExpression = expression.expressions[0];
8
+ const secondExpression = expression.expressions[1];
9
+ if (!firstExpression || !secondExpression)
10
+ return undefined;
11
+ if (!t.isAssignmentExpression(firstExpression, { operator: "=" })) {
12
+ return undefined;
13
+ }
14
+ if (!t.isAssignmentExpression(secondExpression, { operator: "=" })) {
15
+ return undefined;
16
+ }
17
+ return [firstExpression, secondExpression];
18
+ };
19
+ export const getRangeParameterDefaultAssignments = (consequent) => {
20
+ if (t.isExpressionStatement(consequent)) {
21
+ return getSequenceAssignments(consequent.expression);
22
+ }
23
+ if (!t.isBlockStatement(consequent))
24
+ return undefined;
25
+ const statements = consequent.body.filter((statement) => !t.isEmptyStatement(statement));
26
+ if (statements.length === 2) {
27
+ const [firstStatement, secondStatement] = statements;
28
+ if (!firstStatement || !secondStatement)
29
+ return undefined;
30
+ if (!t.isExpressionStatement(firstStatement))
31
+ return undefined;
32
+ if (!t.isExpressionStatement(secondStatement))
33
+ return undefined;
34
+ const firstAssignment = t.isAssignmentExpression(firstStatement.expression, {
35
+ operator: "=",
36
+ })
37
+ ? firstStatement.expression
38
+ : undefined;
39
+ const secondAssignment = t.isAssignmentExpression(secondStatement.expression, {
40
+ operator: "=",
41
+ })
42
+ ? secondStatement.expression
43
+ : undefined;
44
+ if (!firstAssignment || !secondAssignment)
45
+ return undefined;
46
+ return [firstAssignment, secondAssignment];
47
+ }
48
+ if (statements.length === 1) {
49
+ const [onlyStatement] = statements;
50
+ if (!onlyStatement)
51
+ return undefined;
52
+ if (!t.isExpressionStatement(onlyStatement))
53
+ return undefined;
54
+ return getSequenceAssignments(onlyStatement.expression);
55
+ }
56
+ return undefined;
57
+ };
@@ -0,0 +1,8 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Function as BabelFunction } from "@babel/types";
3
+ type RangeParameterNames = {
4
+ startName: string;
5
+ countName: string;
6
+ };
7
+ export declare const getRangeParameterNames: (path: NodePath<BabelFunction>) => RangeParameterNames | undefined;
8
+ export {};
@@ -0,0 +1,54 @@
1
+ import * as t from "@babel/types";
2
+ import { getFirstNonDirectiveStatement } from "./get-first-non-directive-statement.js";
3
+ import { getRangeParameterDefaultAssignments } from "./get-range-parameter-default-assignments.js";
4
+ import { hasCountNonPositiveGuard } from "./has-count-non-positive-guard.js";
5
+ import { hasStartCountSum } from "./has-start-count-sum.js";
6
+ const isNullishParameterCheck = (test, parameterName) => {
7
+ if (!t.isBinaryExpression(test, { operator: "==" }))
8
+ return false;
9
+ const { left, right } = test;
10
+ return ((t.isIdentifier(left, { name: parameterName }) && t.isNullLiteral(right)) ||
11
+ (t.isIdentifier(right, { name: parameterName }) && t.isNullLiteral(left)));
12
+ };
13
+ export const getRangeParameterNames = (path) => {
14
+ const { params, body } = path.node;
15
+ if (!t.isBlockStatement(body))
16
+ return undefined;
17
+ const startParameter = params[0];
18
+ const countParameter = params[1];
19
+ if (!startParameter || !countParameter)
20
+ return undefined;
21
+ if (!t.isIdentifier(startParameter))
22
+ return undefined;
23
+ if (!t.isIdentifier(countParameter))
24
+ return undefined;
25
+ const firstStatement = getFirstNonDirectiveStatement(body);
26
+ if (!firstStatement || !t.isIfStatement(firstStatement))
27
+ return undefined;
28
+ if (firstStatement.alternate)
29
+ return undefined;
30
+ if (!isNullishParameterCheck(firstStatement.test, countParameter.name)) {
31
+ return undefined;
32
+ }
33
+ const assignments = getRangeParameterDefaultAssignments(firstStatement.consequent);
34
+ if (!assignments)
35
+ return undefined;
36
+ const [firstAssignment, secondAssignment] = assignments;
37
+ if (!t.isIdentifier(firstAssignment.left, { name: countParameter.name }) ||
38
+ !t.isIdentifier(firstAssignment.right, { name: startParameter.name })) {
39
+ return undefined;
40
+ }
41
+ if (!t.isIdentifier(secondAssignment.left, { name: startParameter.name }) ||
42
+ !t.isNumericLiteral(secondAssignment.right, { value: 0 })) {
43
+ return undefined;
44
+ }
45
+ if (!hasCountNonPositiveGuard(path, countParameter.name))
46
+ return undefined;
47
+ if (!hasStartCountSum(path, startParameter.name, countParameter.name)) {
48
+ return undefined;
49
+ }
50
+ return {
51
+ startName: startParameter.name,
52
+ countName: countParameter.name,
53
+ };
54
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Function as BabelFunction } from "@babel/types";
3
+ export declare const hasCountNonPositiveGuard: (path: NodePath<BabelFunction>, countName: string) => boolean;
@@ -0,0 +1,49 @@
1
+ import * as t from "@babel/types";
2
+ const isEarlyExit = (statement) => {
3
+ if (t.isReturnStatement(statement))
4
+ return true;
5
+ if (t.isThrowStatement(statement))
6
+ return true;
7
+ if (!t.isBlockStatement(statement))
8
+ return false;
9
+ for (const innerStatement of statement.body) {
10
+ if (t.isEmptyStatement(innerStatement))
11
+ continue;
12
+ return isEarlyExit(innerStatement);
13
+ }
14
+ return false;
15
+ };
16
+ export const hasCountNonPositiveGuard = (path, countName) => {
17
+ const countBinding = path.scope.getBinding(countName);
18
+ if (!countBinding)
19
+ return false;
20
+ let hasGuard = false;
21
+ path.traverse({
22
+ Function(innerPath) {
23
+ innerPath.skip();
24
+ },
25
+ IfStatement(innerPath) {
26
+ if (innerPath.node.alternate)
27
+ return;
28
+ if (!isEarlyExit(innerPath.node.consequent))
29
+ return;
30
+ const test = innerPath.node.test;
31
+ if (!t.isBinaryExpression(test))
32
+ return;
33
+ const { left, right, operator } = test;
34
+ const hasCountLteZero = operator === "<=" &&
35
+ t.isIdentifier(left, { name: countName }) &&
36
+ t.isNumericLiteral(right, { value: 0 }) &&
37
+ innerPath.scope.getBinding(countName) === countBinding;
38
+ const hasZeroGteCount = operator === ">=" &&
39
+ t.isNumericLiteral(left, { value: 0 }) &&
40
+ t.isIdentifier(right, { name: countName }) &&
41
+ innerPath.scope.getBinding(countName) === countBinding;
42
+ if (hasCountLteZero || hasZeroGteCount) {
43
+ hasGuard = true;
44
+ innerPath.stop();
45
+ }
46
+ },
47
+ });
48
+ return hasGuard;
49
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Function as BabelFunction } from "@babel/types";
3
+ export declare const hasStartCountSum: (path: NodePath<BabelFunction>, startName: string, countName: string) => boolean;
@@ -0,0 +1,55 @@
1
+ import * as t from "@babel/types";
2
+ export const hasStartCountSum = (path, startName, countName) => {
3
+ const startBinding = path.scope.getBinding(startName);
4
+ const countBinding = path.scope.getBinding(countName);
5
+ if (!startBinding || !countBinding)
6
+ return false;
7
+ const isStartCountSumExpression = (expression, scope) => {
8
+ if (!t.isBinaryExpression(expression, { operator: "+" }))
9
+ return false;
10
+ const { left, right } = expression;
11
+ const leftIsStart = t.isIdentifier(left, { name: startName }) &&
12
+ scope.getBinding(startName) === startBinding;
13
+ const leftIsCount = t.isIdentifier(left, { name: countName }) &&
14
+ scope.getBinding(countName) === countBinding;
15
+ const rightIsStart = t.isIdentifier(right, { name: startName }) &&
16
+ scope.getBinding(startName) === startBinding;
17
+ const rightIsCount = t.isIdentifier(right, { name: countName }) &&
18
+ scope.getBinding(countName) === countBinding;
19
+ return (leftIsStart && rightIsCount) || (leftIsCount && rightIsStart);
20
+ };
21
+ let hasSum = false;
22
+ path.traverse({
23
+ Function(innerPath) {
24
+ innerPath.skip();
25
+ },
26
+ VariableDeclarator(innerPath) {
27
+ const init = innerPath.node.init;
28
+ if (!init)
29
+ return;
30
+ if (!isStartCountSumExpression(init, innerPath.scope))
31
+ return;
32
+ hasSum = true;
33
+ innerPath.stop();
34
+ },
35
+ AssignmentExpression(innerPath) {
36
+ if (innerPath.node.operator !== "=")
37
+ return;
38
+ if (!isStartCountSumExpression(innerPath.node.right, innerPath.scope)) {
39
+ return;
40
+ }
41
+ hasSum = true;
42
+ innerPath.stop();
43
+ },
44
+ ReturnStatement(innerPath) {
45
+ const argument = innerPath.node.argument;
46
+ if (!argument)
47
+ return;
48
+ if (!isStartCountSumExpression(argument, innerPath.scope))
49
+ return;
50
+ hasSum = true;
51
+ innerPath.stop();
52
+ },
53
+ });
54
+ return hasSum;
55
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "diffReductionImpact": 0,
3
+ "recommended": true,
4
+ "evaluatedAt": "2026-01-25T09:40:26.000Z",
5
+ "notes": "Renames range-style parameters when the count defaults to the start value and start resets to 0.",
6
+ "changedLines": 0
7
+ }
@@ -0,0 +1,2 @@
1
+ import { type Transform } from "../../core/types.js";
2
+ export declare const renameRangeParametersTransform: Transform;
@@ -0,0 +1,50 @@
1
+ import { createRequire } from "node:module";
2
+ import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
3
+ import { getFilesToProcess, } from "../../core/types.js";
4
+ import { getRangeParameterNames } from "./get-range-parameter-names.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 START_BASE_NAME = "start";
9
+ const COUNT_BASE_NAME = "count";
10
+ export const renameRangeParametersTransform = {
11
+ id: "rename-range-parameters",
12
+ description: "Renames optional range parameters to stable $start/$count based on nullish count defaults",
13
+ scope: "file",
14
+ parallelizable: true,
15
+ transform(context) {
16
+ let nodesVisited = 0;
17
+ let transformationsApplied = 0;
18
+ for (const fileInfo of getFilesToProcess(context)) {
19
+ const group = new RenameGroup();
20
+ traverse(fileInfo.ast, {
21
+ Function(path) {
22
+ nodesVisited++;
23
+ const rangeParameters = getRangeParameterNames(path);
24
+ if (!rangeParameters)
25
+ return;
26
+ const { startName, countName } = rangeParameters;
27
+ if (!isStableRenamed(startName)) {
28
+ group.add({
29
+ scope: path.scope,
30
+ currentName: startName,
31
+ baseName: START_BASE_NAME,
32
+ });
33
+ }
34
+ if (!isStableRenamed(countName)) {
35
+ group.add({
36
+ scope: path.scope,
37
+ currentName: countName,
38
+ baseName: COUNT_BASE_NAME,
39
+ });
40
+ }
41
+ },
42
+ });
43
+ transformationsApplied += group.apply();
44
+ }
45
+ return Promise.resolve({
46
+ nodesVisited,
47
+ transformationsApplied,
48
+ });
49
+ },
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Expression, Function as BabelFunction } from "@babel/types";
3
+ export declare const getReturnExpression: (path: NodePath<BabelFunction>) => Expression | undefined;
@@ -0,0 +1,21 @@
1
+ export const getReturnExpression = (path) => {
2
+ const body = path.get("body");
3
+ if (body.isBlockStatement()) {
4
+ const statements = body.get("body");
5
+ if (statements.length !== 1)
6
+ return;
7
+ const [statement] = statements;
8
+ if (!statement)
9
+ return;
10
+ if (!statement.isReturnStatement())
11
+ return;
12
+ const argument = statement.get("argument");
13
+ if (!argument.isExpression())
14
+ return;
15
+ return argument.node;
16
+ }
17
+ if (body.isExpression()) {
18
+ return body.node;
19
+ }
20
+ return;
21
+ };
@@ -0,0 +1,3 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Function as BabelFunction } from "@babel/types";
3
+ export declare const getReturnedFunction: (path: NodePath<BabelFunction>) => NodePath<BabelFunction> | undefined;
@@ -0,0 +1,22 @@
1
+ export const getReturnedFunction = (path) => {
2
+ const body = path.get("body");
3
+ if (body.isBlockStatement()) {
4
+ const statements = body.get("body");
5
+ if (statements.length !== 1)
6
+ return;
7
+ const [statement] = statements;
8
+ if (!statement)
9
+ return;
10
+ if (!statement.isReturnStatement())
11
+ return;
12
+ const argument = statement.get("argument");
13
+ if (!argument.isFunctionExpression() &&
14
+ !argument.isArrowFunctionExpression())
15
+ return;
16
+ return argument;
17
+ }
18
+ if (body.isFunctionExpression() || body.isArrowFunctionExpression()) {
19
+ return body;
20
+ }
21
+ return;
22
+ };
@@ -0,0 +1,2 @@
1
+ import type { Expression, MemberExpression } from "@babel/types";
2
+ export declare const isMemberAccess: (value: Expression, objectName: string, keyName: string) => value is MemberExpression;
@@ -0,0 +1,24 @@
1
+ import { unwrapParentheses } from "./unwrap-parentheses.js";
2
+ export const isMemberAccess = (value, objectName, keyName) => {
3
+ if (value.type !== "MemberExpression")
4
+ return false;
5
+ if (!value.computed)
6
+ return false;
7
+ const object = value.object;
8
+ if (object.type === "Super")
9
+ return false;
10
+ const unwrappedObject = unwrapParentheses(object);
11
+ if (unwrappedObject.type !== "Identifier")
12
+ return false;
13
+ if (unwrappedObject.name !== objectName)
14
+ return false;
15
+ const property = value.property;
16
+ if (property.type === "PrivateName")
17
+ return false;
18
+ const unwrappedProperty = unwrapParentheses(property);
19
+ if (unwrappedProperty.type !== "Identifier")
20
+ return false;
21
+ if (unwrappedProperty.name !== keyName)
22
+ return false;
23
+ return true;
24
+ };
@@ -0,0 +1,4 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Expression, Function as BabelFunction } from "@babel/types";
3
+ export declare const isUndefinedExpression: (expression: Expression, path: NodePath<BabelFunction>) => boolean;
4
+ export declare const isNullishCheck: (test: Expression, identifierName: string, path: NodePath<BabelFunction>) => boolean;
@@ -0,0 +1,74 @@
1
+ import { unwrapParentheses } from "./unwrap-parentheses.js";
2
+ const hasLocalBinding = (path, name) => path.scope.hasBinding(name, { noGlobals: true, noUids: true });
3
+ export const isUndefinedExpression = (expression, path) => {
4
+ const unwrapped = unwrapParentheses(expression);
5
+ if (unwrapped.type === "Identifier" &&
6
+ unwrapped.name === "undefined" &&
7
+ !hasLocalBinding(path, "undefined")) {
8
+ return true;
9
+ }
10
+ if (unwrapped.type === "UnaryExpression" && unwrapped.operator === "void") {
11
+ const argument = unwrapParentheses(unwrapped.argument);
12
+ return argument.type === "NumericLiteral" && argument.value === 0;
13
+ }
14
+ return false;
15
+ };
16
+ const noCoverage = {
17
+ coversNull: false,
18
+ coversUndefined: false,
19
+ };
20
+ const mergeCoverage = (left, right) => {
21
+ return {
22
+ coversNull: left.coversNull || right.coversNull,
23
+ coversUndefined: left.coversUndefined || right.coversUndefined,
24
+ };
25
+ };
26
+ const getNullishCoverage = (test, identifierName, path) => {
27
+ const unwrapped = unwrapParentheses(test);
28
+ if (unwrapped.type === "BinaryExpression") {
29
+ if (unwrapped.operator !== "==" && unwrapped.operator !== "===") {
30
+ return noCoverage;
31
+ }
32
+ const getValueCoverage = (value) => {
33
+ if (value.type === "PrivateName")
34
+ return noCoverage;
35
+ const unwrappedValue = unwrapParentheses(value);
36
+ if (unwrappedValue.type === "NullLiteral") {
37
+ return unwrapped.operator === "=="
38
+ ? { coversNull: true, coversUndefined: true }
39
+ : { coversNull: true, coversUndefined: false };
40
+ }
41
+ if (isUndefinedExpression(unwrappedValue, path)) {
42
+ return unwrapped.operator === "=="
43
+ ? { coversNull: true, coversUndefined: true }
44
+ : { coversNull: false, coversUndefined: true };
45
+ }
46
+ return noCoverage;
47
+ };
48
+ const left = unwrapped.left;
49
+ const right = unwrapped.right;
50
+ if (left.type === "Identifier" && left.name === identifierName) {
51
+ return getValueCoverage(right);
52
+ }
53
+ if (right.type === "Identifier" && right.name === identifierName) {
54
+ return getValueCoverage(left);
55
+ }
56
+ return noCoverage;
57
+ }
58
+ if (unwrapped.type === "LogicalExpression" && unwrapped.operator === "||") {
59
+ const leftCoverage = getNullishCoverage(unwrapped.left, identifierName, path);
60
+ if (!leftCoverage.coversNull && !leftCoverage.coversUndefined) {
61
+ return noCoverage;
62
+ }
63
+ const rightCoverage = getNullishCoverage(unwrapped.right, identifierName, path);
64
+ if (!rightCoverage.coversNull && !rightCoverage.coversUndefined) {
65
+ return noCoverage;
66
+ }
67
+ return mergeCoverage(leftCoverage, rightCoverage);
68
+ }
69
+ return noCoverage;
70
+ };
71
+ export const isNullishCheck = (test, identifierName, path) => {
72
+ const coverage = getNullishCoverage(test, identifierName, path);
73
+ return coverage.coversNull && coverage.coversUndefined;
74
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "diffReductionImpact": -0.000018848364904400228,
3
+ "recommended": false,
4
+ "evaluatedAt": "2026-01-25T10:04:22.417Z",
5
+ "notes": "Negative diff reduction in evaluation; keep optional.",
6
+ "changedLines": 0
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { NodePath } from "@babel/traverse";
2
+ import type { Function as BabelFunction } from "@babel/types";
3
+ type SafePropertyAccessorFactoryMatch = {
4
+ outerParameterName: string;
5
+ innerParameterName: string;
6
+ innerPath: NodePath<BabelFunction>;
7
+ };
8
+ declare const matchSafePropertyAccessorFactory: (outerPath: NodePath<BabelFunction>) => SafePropertyAccessorFactoryMatch | undefined;
9
+ export { matchSafePropertyAccessorFactory };
@@ -0,0 +1,44 @@
1
+ import { getReturnExpression } from "./get-return-expression.js";
2
+ import { getReturnedFunction } from "./get-returned-function.js";
3
+ import { isMemberAccess } from "./is-member-access.js";
4
+ import { isNullishCheck, isUndefinedExpression } from "./is-nullish-check.js";
5
+ import { unwrapParentheses } from "./unwrap-parentheses.js";
6
+ const matchSafePropertyAccessorFactory = (outerPath) => {
7
+ const parameters = outerPath.node.params;
8
+ if (parameters.length !== 1)
9
+ return;
10
+ const outerParameter = parameters[0];
11
+ if (outerParameter?.type !== "Identifier")
12
+ return;
13
+ const innerPath = getReturnedFunction(outerPath);
14
+ if (!innerPath)
15
+ return;
16
+ const innerParameters = innerPath.node.params;
17
+ if (innerParameters.length !== 1)
18
+ return;
19
+ const innerParameter = innerParameters[0];
20
+ if (innerParameter?.type !== "Identifier")
21
+ return;
22
+ if (innerParameter.name === outerParameter.name)
23
+ return;
24
+ const returnExpression = getReturnExpression(innerPath);
25
+ if (!returnExpression)
26
+ return;
27
+ const unwrappedReturnExpression = unwrapParentheses(returnExpression);
28
+ if (unwrappedReturnExpression.type !== "ConditionalExpression")
29
+ return;
30
+ if (!isNullishCheck(unwrappedReturnExpression.test, outerParameter.name, innerPath))
31
+ return;
32
+ if (!isUndefinedExpression(unwrappedReturnExpression.consequent, innerPath))
33
+ return;
34
+ const alternate = unwrapParentheses(unwrappedReturnExpression.alternate);
35
+ if (!isMemberAccess(alternate, outerParameter.name, innerParameter.name)) {
36
+ return;
37
+ }
38
+ return {
39
+ outerParameterName: outerParameter.name,
40
+ innerParameterName: innerParameter.name,
41
+ innerPath,
42
+ };
43
+ };
44
+ export { matchSafePropertyAccessorFactory };
@@ -0,0 +1,3 @@
1
+ import { type Transform } from "../../core/types.js";
2
+ declare const renameSafePropertyAccessorParametersTransform: Transform;
3
+ export { renameSafePropertyAccessorParametersTransform };
@@ -0,0 +1,56 @@
1
+ import { createRequire } from "node:module";
2
+ import { isStableRenamed, RenameGroup } from "../../core/stable-naming.js";
3
+ import { getFilesToProcess, } from "../../core/types.js";
4
+ import { matchSafePropertyAccessorFactory } from "./match-safe-property-accessor-factory.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 OBJECT_BASE_NAME = "object";
9
+ const KEY_BASE_NAME = "key";
10
+ const renameSafePropertyAccessorParametersTransform = {
11
+ id: "rename-safe-property-accessor-parameters",
12
+ description: "Renames parameters in safe property accessor factories to $object/$key",
13
+ scope: "file",
14
+ parallelizable: true,
15
+ transform(context) {
16
+ let nodesVisited = 0;
17
+ let transformationsApplied = 0;
18
+ for (const fileInfo of getFilesToProcess(context)) {
19
+ const group = new RenameGroup();
20
+ traverse(fileInfo.ast, {
21
+ Function(path) {
22
+ nodesVisited++;
23
+ const match = matchSafePropertyAccessorFactory(path);
24
+ if (!match)
25
+ return;
26
+ const outerBinding = path.scope.getBinding(match.outerParameterName);
27
+ if (!outerBinding?.constant)
28
+ return;
29
+ const innerBinding = match.innerPath.scope.getBinding(match.innerParameterName);
30
+ if (!innerBinding?.constant)
31
+ return;
32
+ if (!isStableRenamed(match.innerParameterName)) {
33
+ group.add({
34
+ scope: match.innerPath.scope,
35
+ currentName: match.innerParameterName,
36
+ baseName: KEY_BASE_NAME,
37
+ });
38
+ }
39
+ if (!isStableRenamed(match.outerParameterName)) {
40
+ group.add({
41
+ scope: path.scope,
42
+ currentName: match.outerParameterName,
43
+ baseName: OBJECT_BASE_NAME,
44
+ });
45
+ }
46
+ },
47
+ });
48
+ transformationsApplied += group.apply();
49
+ }
50
+ return Promise.resolve({
51
+ nodesVisited,
52
+ transformationsApplied,
53
+ });
54
+ },
55
+ };
56
+ export { renameSafePropertyAccessorParametersTransform };
@@ -0,0 +1,2 @@
1
+ import type { Expression } from "@babel/types";
2
+ export declare const unwrapParentheses: (expression: Expression) => Expression;
@@ -0,0 +1,7 @@
1
+ export const unwrapParentheses = (expression) => {
2
+ let unwrapped = expression;
3
+ while (unwrapped.type === "ParenthesizedExpression") {
4
+ unwrapped = unwrapped.expression;
5
+ }
6
+ return unwrapped;
7
+ };
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.43.0",
5
+ "version": "1.45.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",