Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions recipes/util-print-to-console-log/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# `util.print, util.puts, util.debug, util.error` DEP0026,DEP0027,DEP0028,DEP0029

This recipe transforms the usage of log functions from util, `print`, `puts`, `debug`, `error` to use `console.log()` or `console.error()`.

See [DEP0026](https://nodejs.org/api/deprecations.html#DEP0026).
See [DEP0027](https://nodejs.org/api/deprecations.html#DEP0027).
See [DEP0028](https://nodejs.org/api/deprecations.html#DEP0028).
See [DEP0029](https://nodejs.org/api/deprecations.html#DEP0029).

## Example

**Before:**

```js
const util = require("node:util");

util.print("Hello world");
util.puts("Hello world");
util.debug("Hello world");
util.error("Hello world");
```

**After:**

```js
console.log("Hello world");
console.log("Hello world");
console.error("Hello world");
console.error("Hello world");
```

**Before:**

```js
const { print, error } = require("node:util");

print("Application started");
error("Processing request");
```

After:

```js
console.log("Application started");
console.error("Processing request");
```
21 changes: 21 additions & 0 deletions recipes/util-print-to-console-log/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
schema_version: "1.0"
name: "@nodejs/util-print-to-console-log"
version: 1.0.0
description: Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`
author: Bruno Rodrigues
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration

registry:
access: public
visibility: public
24 changes: 24 additions & 0 deletions recipes/util-print-to-console-log/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@nodejs/util-print-to-console-log",
"version": "1.0.0",
"description": "Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`",
"type": "module",
"scripts": {
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/userland-migrations.git",
"directory": "recipes/util-print-to-console-log",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "Bruno Rodrigues",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/util-print-to-console-log/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.0.3"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
126 changes: 126 additions & 0 deletions recipes/util-print-to-console-log/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import type {
Edit,
Kinds,
Range,
Rule,
SgNode,
SgRoot,
TypesMap,
} from "@codemod.com/jssg-types/main";
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path";
import { removeBinding } from "@nodejs/codemod-utils/ast-grep/remove-binding";
import { removeLines } from "@nodejs/codemod-utils/ast-grep/remove-lines";

type BindingToReplace = {
rule: Rule<TypesMap>;
node: SgNode<TypesMap, Kinds<TypesMap>>;
binding: string;
replaceFn: (arg: string) => string;
};

const updates = [
{
oldBind: "$.print",
replaceFn: (arg: string) => `console.log(${arg})`,
},
{
oldBind: "$.puts",
replaceFn: (arg: string) => `console.log(${arg})`,
},
{
oldBind: "$.debug",
replaceFn: (arg: string) => `console.error(${arg})`,
},
{
oldBind: "$.error",
replaceFn: (arg: string) => `console.error(${arg})`,
},
];

/*
* Transforms `util.print($$$ARG)` usage to `console.log($$$ARG)`.
* Transforms `util.puts($$$ARG)` usage to `console.log($$$ARG)`.
* Transforms `util.debug($$$ARG)` usage to `console.error($$$ARG)`.
* Transforms `util.error($$$ARG)` usage to `console.error($$$ARG)`.
*
* Steps:
*
* Locate all `util.print|puts|debug|error` import imports, noting the replacement rule, import node, and binding name.
*
* For each binding, replace calls to util.print|puts|debug|error($$$ARG) with the new console.log|error format,
* and determine if the import line should be updated or removed.
*
* Apply all changes, removing or updating the import line as needed.
*/
export default function transform(root: SgRoot): string | null {
const rootNode = root.root();
const edits: Edit[] = [];
const linesToRemove: Range[] = [];
const bindsToReplace: BindingToReplace[] = [];

const nodeRequires = getNodeRequireCalls(root, "util");
const nodeImports = getNodeImportStatements(root, "util");
const importRequireStatement = [...nodeRequires, ...nodeImports];

if (!importRequireStatement.length) return null;

for (const node of importRequireStatement) {
for (const update of updates) {
const bind = resolveBindingPath(node, update.oldBind);

// if `fn` function ins't coming from `node:util`
if (!bind) continue;

bindsToReplace.push({
rule: {
pattern: `${bind}($$$ARG)`,
},
node,
binding: bind,
replaceFn: update.replaceFn,
});
}
}

for (const bind of bindsToReplace) {
const matches = rootNode.findAll({
rule: bind.rule,
});

for (const match of matches) {
const args = match.getMultipleMatches("ARG");

const argsStr = args
.map((arg) => {
const text = arg.text();
if (text === ",") {
// if arg is a comman, add a space at end
return text.padEnd(2, " ");
}
return text;
})
.join("");

const replace = match.replace(bind.replaceFn(argsStr));
edits.push(replace);

const result = removeBinding(bind.node, bind.binding.split(".").at(0));

if (result?.lineToRemove) {
linesToRemove.push(result.lineToRemove);
}

if (result?.edit) {
edits.push(result.edit);
}
}
}

if (!edits.length) return;

const sourceCode = rootNode.commitEdits(edits);

return removeLines(sourceCode, linesToRemove);
}
2 changes: 2 additions & 0 deletions recipes/util-print-to-console-log/tests/expected/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.error("Hello world");
2 changes: 2 additions & 0 deletions recipes/util-print-to-console-log/tests/expected/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.error("Hello world");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("Server listening on port 3000");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("Debug message");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("User:", "john", "logged in");
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { inspect } = require("node:util");

console.log("Starting application");
const formatted = inspect({ foo: "bar" });
console.log(formatted);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

console.log("Application started");
console.log("Processing request");
2 changes: 2 additions & 0 deletions recipes/util-print-to-console-log/tests/expected/print.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("Hello world");
2 changes: 2 additions & 0 deletions recipes/util-print-to-console-log/tests/expected/puts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("Hello world");
16 changes: 16 additions & 0 deletions recipes/util-print-to-console-log/tests/expected/real-scenario.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import http from "node:http";

console.log("Debug message");

const PORT = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
console.log(`[${req.method}] ${req.url}`);
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World\n");
});

server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

console.log("Hello world");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

const port = 3000;
console.log(`Server started on port ${port}`);
3 changes: 3 additions & 0 deletions recipes/util-print-to-console-log/tests/input/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.debug("Hello world");
3 changes: 3 additions & 0 deletions recipes/util-print-to-console-log/tests/input/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.error("Hello world");
3 changes: 3 additions & 0 deletions recipes/util-print-to-console-log/tests/input/esm-import.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { print } from "node:util";

print("Server listening on port 3000");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as util from "node:util";

util.print("Debug message");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.print("User:", "john", "logged in");
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { print, inspect } = require("node:util");

print("Starting application");
const formatted = inspect({ foo: "bar" });
console.log(formatted);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { print } = require("node:util");

print("Application started");
print("Processing request");
3 changes: 3 additions & 0 deletions recipes/util-print-to-console-log/tests/input/print.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.print("Hello world");
3 changes: 3 additions & 0 deletions recipes/util-print-to-console-log/tests/input/puts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.puts("Hello world");
17 changes: 17 additions & 0 deletions recipes/util-print-to-console-log/tests/input/real-scenario.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import util from "node:util";
import http from "node:http";

util.print("Debug message");

const PORT = process.env.PORT || 3000;

const server = http.createServer((req, res) => {
util.print(`[${req.method}] ${req.url}`);
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello World\n");
});

server.listen(PORT, () => {
util.print(`Server running at http://localhost:${PORT}/`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const util = require("node:util");

util.print("Hello world");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { print } = require("node:util");

const port = 3000;
print(`Server started on port ${port}`);
25 changes: 25 additions & 0 deletions recipes/util-print-to-console-log/workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json

version: "1"

nodes:
- id: apply-transforms
name: Apply AST Transformations
type: automatic
steps:
- name: Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`
js-ast-grep:
js_file: src/workflow.ts
base_path: .
include:
- "**/*.cjs"
- "**/*.cts"
- "**/*.js"
- "**/*.jsx"
- "**/*.mjs"
- "**/*.mts"
- "**/*.ts"
- "**/*.tsx"
exclude:
- "**/node_modules/**"
language: typescript
Loading