Skip to content

Commit 06a18ea

Browse files
feat(outgoingmessage-headers)
1 parent 40a0bdc commit 06a18ea

File tree

6 files changed

+215
-0
lines changed

6 files changed

+215
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
schema_version: "1.0"
2+
name: "@nodejs/http-outgoingmessage-headers"
3+
version: 1.0.0
4+
description: Migrate deprecated usages of OutgoingMessage.prototype._headers and ._headerNames to public HTTP header APIs
5+
author: Node.js Team
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+
- http
19+
20+
registry:
21+
access: public
22+
visibility: public
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@nodejs/http-outgoingmessage-headers",
3+
"version": "1.0.0",
4+
"description": "Migrate deprecated usages of OutgoingMessage.prototype._headers and ._headerNames to the official HTTP header APIs.",
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/http-outgoingmessage-headers",
13+
"bugs": "https://github.com/nodejs/userland-migrations/issues"
14+
},
15+
"author": "elvessilvavieira (Elves Vieira)",
16+
"license": "MIT",
17+
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/http-outgoingmessage-headers/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 type { SgRoot, Edit } from "@codemod.com/jssg-types/main";
2+
import type Js from "@codemod.com/jssg-types/langs/javascript";
3+
4+
export default function transform(root: SgRoot<Js>): string | null {
5+
const rootNode = root.root();
6+
const edits: Edit[] = [];
7+
8+
{
9+
const matches = rootNode.findAll({
10+
rule: { pattern: "$OBJ._headers[$KEY]" },
11+
});
12+
for (const m of matches) {
13+
const obj = m.getMatch("OBJ");
14+
const key = m.getMatch("KEY");
15+
if (!obj || !key) continue;
16+
17+
const name = obj.text();
18+
if (!looksLikeOutgoingMessage(rootNode, name)) continue;
19+
20+
edits.push(m.replace(`${obj.text()}.getHeader(${key.text()})`));
21+
}
22+
}
23+
24+
{
25+
const matches = rootNode.findAll({
26+
rule: { pattern: "$KEY in $OBJ._headers" },
27+
});
28+
for (const m of matches) {
29+
const obj = m.getMatch("OBJ");
30+
const key = m.getMatch("KEY");
31+
if (!obj || !key) continue;
32+
33+
const name = obj.text();
34+
if (!looksLikeOutgoingMessage(rootNode, name)) continue;
35+
36+
edits.push(m.replace(`${obj.text()}.hasHeader(${key.text()})`));
37+
}
38+
}
39+
40+
{
41+
const matches = rootNode.findAll({
42+
rule: { pattern: "Object.keys($OBJ._headers)" },
43+
});
44+
for (const m of matches) {
45+
const obj = m.getMatch("OBJ");
46+
if (!obj) continue;
47+
48+
const name = obj.text();
49+
if (!looksLikeOutgoingMessage(rootNode, name)) continue;
50+
51+
edits.push(m.replace(`${obj.text()}.getHeaderNames()`));
52+
}
53+
}
54+
55+
{
56+
const matches = rootNode.findAll({
57+
rule: { pattern: "$OBJ._headerNames" },
58+
});
59+
for (const m of matches) {
60+
const obj = m.getMatch("OBJ");
61+
if (!obj) continue;
62+
63+
const name = obj.text();
64+
if (!looksLikeOutgoingMessage(rootNode, name)) continue;
65+
66+
edits.push(m.replace(`${obj.text()}.getHeaderNames()`));
67+
}
68+
}
69+
70+
{
71+
const matches = rootNode.findAll({
72+
rule: { pattern: "$OBJ._headers" },
73+
});
74+
for (const m of matches) {
75+
const obj = m.getMatch("OBJ");
76+
if (!obj) continue;
77+
78+
const name = obj.text();
79+
if (!looksLikeOutgoingMessage(rootNode, name)) continue;
80+
81+
const text = m.text();
82+
if (text.includes("[")) continue;
83+
84+
const parent = m.parent();
85+
const parentText = parent?.text() ?? "";
86+
87+
if (parentText.startsWith("Object.keys(")) continue;
88+
89+
if (parentText.includes("=") && parentText.trim().startsWith(obj.text())) {
90+
continue;
91+
}
92+
93+
edits.push(m.replace(`${obj.text()}.getHeaders()`));
94+
}
95+
}
96+
97+
if (!edits.length) return null;
98+
return rootNode.commitEdits(edits);
99+
}
100+
101+
function looksLikeOutgoingMessage(
102+
file: ReturnType<SgRoot<Js>["root"]>,
103+
name: string
104+
): boolean {
105+
const methods = [
106+
"setHeader",
107+
"getHeader",
108+
"getHeaders",
109+
"hasHeader",
110+
"removeHeader",
111+
"writeHead",
112+
];
113+
for (const m of methods) {
114+
const hit = file.find({ rule: { pattern: `${name}.${m}($$)` } });
115+
if (hit) return true;
116+
}
117+
return false;
118+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function example(response, request) {
2+
const all = response.getHeaders();
3+
const ct = response.getHeader("content-type");
4+
if (response.hasHeader("content-length")) console.log("has length");
5+
console.log(response.getHeaderNames());
6+
console.log(response.getHeaderNames());
7+
8+
const allReq = request.getHeaders();
9+
const ua = request.getHeader('user-agent');
10+
if (request.hasHeader('accept')) console.log('has accept');
11+
console.log(request.getHeaderNames());
12+
console.log(request.getHeaderNames());
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function example(response, request) {
2+
const all = response._headers;
3+
const ct = response._headers["content-type"];
4+
if ("content-length" in response._headers) console.log("has length");
5+
console.log(Object.keys(response._headers));
6+
console.log(response._headerNames);
7+
8+
const allReq = request._headers;
9+
const ua = request._headers['user-agent'];
10+
if ('accept' in request._headers) console.log('has accept');
11+
console.log(Object.keys(request._headers));
12+
console.log(request._headerNames);
13+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json
2+
3+
version: "1"
4+
5+
nodes:
6+
- id: apply-transforms
7+
name: Apply AST Transformations
8+
type: automatic
9+
steps:
10+
- name: Replace deprecated OutgoingMessage private header fields.
11+
js-ast-grep:
12+
js_file: src/workflow.ts
13+
base_path: .
14+
include:
15+
- "**/*.js"
16+
- "**/*.jsx"
17+
- "**/*.mjs"
18+
- "**/*.cjs"
19+
- "**/*.cts"
20+
- "**/*.mts"
21+
- "**/*.ts"
22+
- "**/*.tsx"
23+
exclude:
24+
- "**/node_modules/**"
25+
language: typescript

0 commit comments

Comments
 (0)