Skip to content

Commit b1e9a9d

Browse files
Feat/util print to console log (#186)
Co-authored-by: Augustin Mauroy <[email protected]>
1 parent df518fc commit b1e9a9d

32 files changed

+398
-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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# `util.print, util.puts, util.debug, util.error` DEP0026,DEP0027,DEP0028,DEP0029
2+
3+
This recipe transforms the usage of log functions from util, `print`, `puts`, `debug`, `error` to use `console.log()` or `console.error()`.
4+
5+
See [DEP0026](https://nodejs.org/api/deprecations.html#DEP0026).
6+
See [DEP0027](https://nodejs.org/api/deprecations.html#DEP0027).
7+
See [DEP0028](https://nodejs.org/api/deprecations.html#DEP0028).
8+
See [DEP0029](https://nodejs.org/api/deprecations.html#DEP0029).
9+
10+
## Example
11+
12+
**Before:**
13+
14+
```js
15+
const util = require("node:util");
16+
17+
util.print("Hello world");
18+
util.puts("Hello world");
19+
util.debug("Hello world");
20+
util.error("Hello world");
21+
```
22+
23+
**After:**
24+
25+
```js
26+
console.log("Hello world");
27+
console.log("Hello world");
28+
console.error("Hello world");
29+
console.error("Hello world");
30+
```
31+
32+
**Before:**
33+
34+
```js
35+
const { print, error } = require("node:util");
36+
37+
print("Application started");
38+
error("Processing request");
39+
```
40+
41+
After:
42+
43+
```js
44+
console.log("Application started");
45+
console.error("Processing request");
46+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
schema_version: "1.0"
2+
name: "@nodejs/util-print-to-console-log"
3+
version: 1.0.0
4+
description: Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`
5+
author: Bruno Rodrigues
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+
19+
registry:
20+
access: public
21+
visibility: public
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@nodejs/util-print-to-console-log",
3+
"version": "1.0.0",
4+
"description": "Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`",
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/util-print-to-console-log",
13+
"bugs": "https://github.com/nodejs/userland-migrations/issues"
14+
},
15+
"author": "Bruno Rodrigues",
16+
"license": "MIT",
17+
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/util-print-to-console-log/README.md",
18+
"devDependencies": {
19+
"@codemod.com/jssg-types": "^1.0.3"
20+
},
21+
"dependencies": {
22+
"@nodejs/codemod-utils": "*"
23+
}
24+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type {
2+
Edit,
3+
Kinds,
4+
Range,
5+
Rule,
6+
SgNode,
7+
SgRoot,
8+
TypesMap,
9+
} from "@codemod.com/jssg-types/main";
10+
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
11+
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
12+
import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path";
13+
import { removeBinding } from "@nodejs/codemod-utils/ast-grep/remove-binding";
14+
import { removeLines } from "@nodejs/codemod-utils/ast-grep/remove-lines";
15+
16+
type BindingToReplace = {
17+
rule: Rule<TypesMap>;
18+
node: SgNode<TypesMap, Kinds<TypesMap>>;
19+
binding: string;
20+
replaceFn: (arg: string) => string;
21+
};
22+
23+
const updates = [
24+
{
25+
oldBind: "$.print",
26+
replaceFn: (arg: string) => `console.log(${arg})`,
27+
},
28+
{
29+
oldBind: "$.puts",
30+
replaceFn: (arg: string) => `console.log(${arg})`,
31+
},
32+
{
33+
oldBind: "$.debug",
34+
replaceFn: (arg: string) => `console.error(${arg})`,
35+
},
36+
{
37+
oldBind: "$.error",
38+
replaceFn: (arg: string) => `console.error(${arg})`,
39+
},
40+
];
41+
42+
/*
43+
* Transforms `util.print($$$ARG)` usage to `console.log($$$ARG)`.
44+
* Transforms `util.puts($$$ARG)` usage to `console.log($$$ARG)`.
45+
* Transforms `util.debug($$$ARG)` usage to `console.error($$$ARG)`.
46+
* Transforms `util.error($$$ARG)` usage to `console.error($$$ARG)`.
47+
*
48+
* Steps:
49+
*
50+
* Locate all `util.print|puts|debug|error` import imports, noting the replacement rule, import node, and binding name.
51+
*
52+
* For each binding, replace calls to util.print|puts|debug|error($$$ARG) with the new console.log|error format,
53+
* and determine if the import line should be updated or removed.
54+
*
55+
* Apply all changes, removing or updating the import line as needed.
56+
*/
57+
export default function transform(root: SgRoot): string | null {
58+
const rootNode = root.root();
59+
const edits: Edit[] = [];
60+
const linesToRemove: Range[] = [];
61+
const bindsToReplace: BindingToReplace[] = [];
62+
63+
const nodeRequires = getNodeRequireCalls(root, "util");
64+
const nodeImports = getNodeImportStatements(root, "util");
65+
const importRequireStatement = [...nodeRequires, ...nodeImports];
66+
67+
if (!importRequireStatement.length) return null;
68+
69+
for (const node of importRequireStatement) {
70+
for (const update of updates) {
71+
const bind = resolveBindingPath(node, update.oldBind);
72+
73+
// if `fn` function ins't coming from `node:util`
74+
if (!bind) continue;
75+
76+
bindsToReplace.push({
77+
rule: {
78+
pattern: `${bind}($$$ARG)`,
79+
},
80+
node,
81+
binding: bind,
82+
replaceFn: update.replaceFn,
83+
});
84+
}
85+
}
86+
87+
for (const bind of bindsToReplace) {
88+
const matches = rootNode.findAll({
89+
rule: bind.rule,
90+
});
91+
92+
for (const match of matches) {
93+
const args = match.getMultipleMatches("ARG");
94+
95+
const argsStr = args
96+
.map((arg) => {
97+
const text = arg.text();
98+
if (text === ",") {
99+
// if arg is a comman, add a space at end
100+
return text.padEnd(2, " ");
101+
}
102+
return text;
103+
})
104+
.join("");
105+
106+
const replace = match.replace(bind.replaceFn(argsStr));
107+
edits.push(replace);
108+
109+
const result = removeBinding(bind.node, bind.binding.split(".").at(0));
110+
111+
if (result?.lineToRemove) {
112+
linesToRemove.push(result.lineToRemove);
113+
}
114+
115+
if (result?.edit) {
116+
edits.push(result.edit);
117+
}
118+
}
119+
}
120+
121+
if (!edits.length) return;
122+
123+
const sourceCode = rootNode.commitEdits(edits);
124+
125+
return removeLines(sourceCode, linesToRemove);
126+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
console.error("Hello world");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
console.error("Hello world");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
console.log("Server listening on port 3000");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
console.log("Debug message");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
console.log("User:", "john", "logged in");

0 commit comments

Comments
 (0)