Skip to content

Commit 0a41672

Browse files
Eliekhoury17CopilotAugustinMauroyJakobJingleheimerbrunocroh
authored
feat(bytesRead-to-bytesWritten): implement Node.js DEP0108 migration (#224)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Augustin Mauroy <[email protected]> Co-authored-by: Jacob Smith <[email protected]> Co-authored-by: Bruno Rodrigues <[email protected]>
1 parent c7f2a30 commit 0a41672

24 files changed

+479
-0
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# `zlib.bytesRead``zlib.bytesWritten` DEP0108
2+
3+
This codemod replaces zlib.bytesRead with zlib.bytesWritten for consistent stream property naming. It's useful to migrate code that uses the deprecated property which has been removed.
4+
5+
It replaces zlib.bytesRead with zlib.bytesWritten in all zlib transform streams and it handles both CommonJS and ESM imports.
6+
7+
See [DEP0108](https://nodejs.org/api/deprecations.html#DEP0108).
8+
9+
---
10+
11+
## Example
12+
13+
**Case 1**
14+
15+
Before:
16+
17+
```js
18+
const zlib = require("node:zlib");
19+
const gzip = zlib.createGzip();
20+
gzip.on("end", () => {
21+
console.log("Bytes processed:", gzip.bytesRead);
22+
});
23+
```
24+
25+
After:
26+
27+
```js
28+
const zlib = require("node:zlib");
29+
const gzip = zlib.createGzip();
30+
gzip.on("end", () => {
31+
console.log("Bytes processed:", gzip.bytesWritten);
32+
});
33+
```
34+
35+
**Case 2**
36+
37+
Before:
38+
39+
```js
40+
const zlib = require("node:zlib");
41+
const deflate = zlib.createDeflate();
42+
deflate.on("finish", () => {
43+
const stats = {
44+
input: deflate.bytesRead,
45+
output: deflate.bytesWritten
46+
};
47+
});
48+
```
49+
50+
After:
51+
52+
```js
53+
const zlib = require("node:zlib");
54+
const deflate = zlib.createDeflate();
55+
deflate.on("finish", () => {
56+
const stats = {
57+
input: deflate.bytesWritten,
58+
output: deflate.bytesWritten
59+
};
60+
});
61+
```
62+
63+
**Case 3**
64+
65+
Before:
66+
67+
```js
68+
const zlib = require("node:zlib");
69+
function trackProgress(stream) {
70+
setInterval(() => {
71+
console.log(`Progress: ${stream.bytesRead} bytes`);
72+
}, 1000);
73+
}
74+
```
75+
76+
After:
77+
78+
```js
79+
const zlib = require("node:zlib");
80+
function trackProgress(stream) {
81+
setInterval(() => {
82+
console.log(`Progress: ${stream.bytesWritten} bytes`);
83+
}, 1000);
84+
}
85+
```
86+
87+
**Case 4**
88+
89+
Before:
90+
91+
```js
92+
import { createGzip } from "node:zlib";
93+
const gzip = createGzip();
94+
const bytesProcessed = gzip.bytesRead;
95+
```
96+
97+
After:
98+
99+
```js
100+
import { createGzip } from "node:zlib";
101+
const gzip = createGzip();
102+
const bytesProcessed = gzip.bytesWritten;
103+
```
104+
105+
**Case 5**
106+
107+
Before:
108+
109+
```js
110+
const zlib = require("node:zlib");
111+
const gzip = zlib.createGzip();
112+
const processed = gzip.bytesRead;
113+
```
114+
115+
After:
116+
117+
```js
118+
const zlib = require("node:zlib");
119+
const gzip = zlib.createGzip();
120+
const processed = gzip.bytesWritten;
121+
```
122+
123+
**Case 6**
124+
125+
Before:
126+
127+
```js
128+
const { createGzip } = require("node:zlib");
129+
const gzip = createGzip();
130+
const bytes = gzip.bytesRead;
131+
```
132+
133+
After:
134+
135+
```js
136+
const { createGzip } = require("node:zlib");
137+
const gzip = createGzip();
138+
const bytes = gzip.bytesWritten;
139+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
schema_version: "1.0"
2+
name: "@nodejs/zlib-bytesread-to-byteswritten"
3+
version: 1.0.0
4+
description: Handle DEP0108 by replacing deprecated `zlib.bytesRead` with `zlib.bytesWritten` in Node.js transform streams
5+
author: Elie Khoury
6+
license: MIT
7+
workflow: workflow.yaml
8+
category: migration
9+
10+
targets:
11+
languages:
12+
- javascript
13+
- typescript
14+
15+
keywords:
16+
- transformation
17+
- migration
18+
- zlib
19+
- bytesRead
20+
- bytesWritten
21+
22+
registry:
23+
access: public
24+
visibility: public
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@nodejs/zlib-bytesread-to-byteswritten",
3+
"version": "1.0.0",
4+
"description": "Replace deprecated `zlib.bytesRead` with `zlib.bytesWritten` in Node.js transform streams",
5+
"type": "module",
6+
"scripts": {
7+
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/nodejs/userland-migrations.git",
12+
"directory": "recipes/zlib-bytesread-to-byteswritten",
13+
"bugs": "https://github.com/nodejs/userland-migrations/issues"
14+
},
15+
"author": "Elie Khoury",
16+
"license": "MIT",
17+
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/zlib-bytesread-to-byteswritten/README.md",
18+
"devDependencies": {
19+
"@codemod.com/jssg-types": "^1.0.9"
20+
},
21+
"dependencies": {
22+
"@nodejs/codemod-utils": "*"
23+
}
24+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call';
2+
import {
3+
getNodeImportCalls,
4+
getNodeImportStatements,
5+
} from '@nodejs/codemod-utils/ast-grep/import-statement';
6+
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';
7+
import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines';
8+
import type { Edit, Range, SgRoot } from '@codemod.com/jssg-types/main';
9+
import type Js from '@codemod.com/jssg-types/langs/javascript';
10+
11+
const ZLIB_FACTORIES = [
12+
'createGzip',
13+
'createGunzip',
14+
'createDeflate',
15+
'createInflate',
16+
'createBrotliCompress',
17+
'createBrotliDecompress',
18+
'createUnzip',
19+
];
20+
21+
const FUNC_KINDS = [
22+
'function_declaration',
23+
'function_expression',
24+
'arrow_function',
25+
] as const;
26+
27+
export default function transform(root: SgRoot<Js>): string | null {
28+
const rootNode = root.root();
29+
const edits: Edit[] = [];
30+
const linesToRemove: Range[] = [];
31+
32+
// 1 Find all static and dynamic zlib imports/requires
33+
const importNodes = [
34+
...getNodeRequireCalls(root, 'node:zlib'),
35+
...getNodeImportStatements(root, 'node:zlib'),
36+
...getNodeImportCalls(root, 'node:zlib'),
37+
];
38+
39+
const factoryBindings = new Set<string>();
40+
const streamVariables: string[] = [];
41+
42+
// 1.a Resolve all local bindings from "node:zlib"
43+
for (const node of importNodes) {
44+
for (const factory of ZLIB_FACTORIES) {
45+
const binding = resolveBindingPath(node, `$.${factory}`);
46+
if (binding) factoryBindings.add(binding);
47+
}
48+
}
49+
50+
// If no import is found that means we can skip transformation on this file
51+
if (!importNodes.length) return null;
52+
53+
// 2 Track variables assigned from factories (const, let, var)
54+
for (const binding of factoryBindings) {
55+
const matches = rootNode.findAll({
56+
rule: {
57+
kind: 'variable_declarator',
58+
has: {
59+
field: 'value',
60+
kind: 'call_expression',
61+
pattern: `${binding}($$$ARGS)`,
62+
},
63+
},
64+
});
65+
66+
for (const match of matches) {
67+
const varMatch = match.field('name');
68+
69+
if (varMatch) {
70+
const varName = varMatch.text();
71+
if (!streamVariables.includes(varName)) streamVariables.push(varName);
72+
}
73+
}
74+
}
75+
76+
// 3 Replace .bytesRead → .bytesWritten for tracked variables
77+
for (const variable of streamVariables) {
78+
const matches = rootNode.findAll({
79+
rule: { pattern: `${variable}.bytesRead` },
80+
});
81+
82+
for (const match of matches) {
83+
edits.push(
84+
match.replace(match.text().replace('.bytesRead', '.bytesWritten')),
85+
);
86+
}
87+
}
88+
89+
// Step 4: Replace .bytesRead → .bytesWritten for function parameters
90+
for (const kind of FUNC_KINDS) {
91+
const funcs = rootNode.findAll({ rule: { kind } });
92+
93+
for (const func of funcs) {
94+
const paramNames = func
95+
.field('parameters')
96+
?.findAll({ rule: { kind: 'identifier' } });
97+
98+
for (const paramName of paramNames) {
99+
// replace member_expressions that use ${paramName}.bytesRead inside the function context
100+
const matches = func.findAll({
101+
rule: {
102+
kind: 'member_expression',
103+
pattern: `${paramName.text()}.bytesRead`,
104+
},
105+
});
106+
for (const match of matches) {
107+
edits.push(
108+
match.replace(match.text().replace('.bytesRead', '.bytesWritten')),
109+
);
110+
}
111+
}
112+
}
113+
}
114+
115+
if (!edits.length) return null;
116+
117+
return removeLines(rootNode.commitEdits(edits), linesToRemove);
118+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const zlib = require("node:zlib");
2+
const gzip = zlib.createGzip();
3+
const processed = gzip.bytesWritten;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const zlib = require("node:zlib");
2+
const deflate = zlib.createDeflate();
3+
deflate.on("finish", () => {
4+
const stats = {
5+
input: deflate.bytesWritten,
6+
output: deflate.bytesWritten
7+
};
8+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const { createGzip } = require("node:zlib");
2+
const gzip = createGzip();
3+
const bytes = gzip.bytesWritten;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// CommonJS style dynamic import
2+
async function testCommonJS() {
3+
const zlib = await import("node:zlib");
4+
const gzip = zlib.createGzip();
5+
console.log(gzip.bytesWritten);
6+
}
7+
8+
async function testCommonJSLet() {
9+
let zlib = await import("node:zlib");
10+
const gzip = zlib.createGzip();
11+
console.log(gzip.bytesWritten);
12+
}
13+
14+
async function testCommonJSVar() {
15+
var zlib = await import("node:zlib");
16+
const gzip = zlib.createGzip();
17+
console.log(gzip.bytesWritten);
18+
}
19+
20+
// ESM style dynamic import
21+
const zlibESM = await import("node:zlib");
22+
const gzipESM = zlibESM.createGzip();
23+
console.log(gzipESM.bytesWritten);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createGzip } from "node:zlib";
2+
const gzip = createGzip();
3+
const bytesProcessed = gzip.bytesWritten;

0 commit comments

Comments
 (0)