From c674328bc701f1ded8ea9731272099a3185e09fe Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 13:33:39 +0100 Subject: [PATCH 1/7] feat(util-print-to-console.log): introduce --- recipes/util-print-to-console-log/README.md | 46 +++++++ .../util-print-to-console-log/codemod.yaml | 21 +++ .../util-print-to-console-log/package.json | 24 ++++ .../util-print-to-console-log/src/workflow.ts | 123 ++++++++++++++++++ .../tests/expected/debug.js | 2 + .../tests/expected/error.js | 2 + .../tests/expected/esm-import.mjs | 2 + .../tests/expected/esm-namespace-import.mjs | 2 + .../tests/expected/multiple-arguments.js | 2 + .../tests/expected/multiple-imports.cjs | 5 + .../tests/expected/named-import-print-only.js | 3 + .../tests/expected/print.js | 2 + .../tests/expected/puts.js | 2 + .../tests/expected/real-scenario.js | 16 +++ .../tests/expected/simple-scenario.js | 2 + .../tests/expected/with-interpolation.js | 3 + .../tests/input/debug.js | 3 + .../tests/input/error.js | 3 + .../tests/input/esm-import.mjs | 3 + .../tests/input/esm-namespace-import.mjs | 3 + .../tests/input/multiple-arguments.js | 3 + .../tests/input/multiple-imports.cjs | 5 + .../tests/input/named-import-print-only.js | 4 + .../tests/input/print.js | 3 + .../tests/input/puts.js | 3 + .../tests/input/real-scenario.js | 17 +++ .../tests/input/simple-scenario.js | 3 + .../tests/input/with-interpolation.js | 4 + .../tests/named-import-print-only.js | 3 + .../util-print-to-console-log/workflow.yaml | 25 ++++ 30 files changed, 339 insertions(+) create mode 100644 recipes/util-print-to-console-log/README.md create mode 100644 recipes/util-print-to-console-log/codemod.yaml create mode 100644 recipes/util-print-to-console-log/package.json create mode 100644 recipes/util-print-to-console-log/src/workflow.ts create mode 100644 recipes/util-print-to-console-log/tests/expected/debug.js create mode 100644 recipes/util-print-to-console-log/tests/expected/error.js create mode 100644 recipes/util-print-to-console-log/tests/expected/esm-import.mjs create mode 100644 recipes/util-print-to-console-log/tests/expected/esm-namespace-import.mjs create mode 100644 recipes/util-print-to-console-log/tests/expected/multiple-arguments.js create mode 100644 recipes/util-print-to-console-log/tests/expected/multiple-imports.cjs create mode 100644 recipes/util-print-to-console-log/tests/expected/named-import-print-only.js create mode 100644 recipes/util-print-to-console-log/tests/expected/print.js create mode 100644 recipes/util-print-to-console-log/tests/expected/puts.js create mode 100644 recipes/util-print-to-console-log/tests/expected/real-scenario.js create mode 100644 recipes/util-print-to-console-log/tests/expected/simple-scenario.js create mode 100644 recipes/util-print-to-console-log/tests/expected/with-interpolation.js create mode 100644 recipes/util-print-to-console-log/tests/input/debug.js create mode 100644 recipes/util-print-to-console-log/tests/input/error.js create mode 100644 recipes/util-print-to-console-log/tests/input/esm-import.mjs create mode 100644 recipes/util-print-to-console-log/tests/input/esm-namespace-import.mjs create mode 100644 recipes/util-print-to-console-log/tests/input/multiple-arguments.js create mode 100644 recipes/util-print-to-console-log/tests/input/multiple-imports.cjs create mode 100644 recipes/util-print-to-console-log/tests/input/named-import-print-only.js create mode 100644 recipes/util-print-to-console-log/tests/input/print.js create mode 100644 recipes/util-print-to-console-log/tests/input/puts.js create mode 100644 recipes/util-print-to-console-log/tests/input/real-scenario.js create mode 100644 recipes/util-print-to-console-log/tests/input/simple-scenario.js create mode 100644 recipes/util-print-to-console-log/tests/input/with-interpolation.js create mode 100644 recipes/util-print-to-console-log/tests/named-import-print-only.js create mode 100644 recipes/util-print-to-console-log/workflow.yaml diff --git a/recipes/util-print-to-console-log/README.md b/recipes/util-print-to-console-log/README.md new file mode 100644 index 00000000..f2ecc2c3 --- /dev/null +++ b/recipes/util-print-to-console-log/README.md @@ -0,0 +1,46 @@ +# `util.print, util.puts, util.debug, util.error` DEP0059,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"); +``` diff --git a/recipes/util-print-to-console-log/codemod.yaml b/recipes/util-print-to-console-log/codemod.yaml new file mode 100644 index 00000000..24009f41 --- /dev/null +++ b/recipes/util-print-to-console-log/codemod.yaml @@ -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 diff --git a/recipes/util-print-to-console-log/package.json b/recipes/util-print-to-console-log/package.json new file mode 100644 index 00000000..fdd03d92 --- /dev/null +++ b/recipes/util-print-to-console-log/package.json @@ -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 --update-snapshots -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": "*" + } +} diff --git a/recipes/util-print-to-console-log/src/workflow.ts b/recipes/util-print-to-console-log/src/workflow.ts new file mode 100644 index 00000000..b0dcf043 --- /dev/null +++ b/recipes/util-print-to-console-log/src/workflow.ts @@ -0,0 +1,123 @@ +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; + node: SgNode>; + 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.log($$$ARG)` usage to `console.log(new Date().toLocaleString(), $$$ARG)`. + * + * Steps: + * + * Locate all util.log imports, noting the replacement rule, import node, and binding name. + * + * For each binding, replace calls to util.log($$$ARG) with the new console.log 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 `log` 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); +} diff --git a/recipes/util-print-to-console-log/tests/expected/debug.js b/recipes/util-print-to-console-log/tests/expected/debug.js new file mode 100644 index 00000000..16c7cf20 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/debug.js @@ -0,0 +1,2 @@ + +console.error("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/expected/error.js b/recipes/util-print-to-console-log/tests/expected/error.js new file mode 100644 index 00000000..16c7cf20 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/error.js @@ -0,0 +1,2 @@ + +console.error("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/expected/esm-import.mjs b/recipes/util-print-to-console-log/tests/expected/esm-import.mjs new file mode 100644 index 00000000..3fd202c3 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/esm-import.mjs @@ -0,0 +1,2 @@ + +console.log("Server listening on port 3000"); diff --git a/recipes/util-print-to-console-log/tests/expected/esm-namespace-import.mjs b/recipes/util-print-to-console-log/tests/expected/esm-namespace-import.mjs new file mode 100644 index 00000000..214701e1 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/esm-namespace-import.mjs @@ -0,0 +1,2 @@ + +console.log("Debug message"); diff --git a/recipes/util-print-to-console-log/tests/expected/multiple-arguments.js b/recipes/util-print-to-console-log/tests/expected/multiple-arguments.js new file mode 100644 index 00000000..819d6080 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/multiple-arguments.js @@ -0,0 +1,2 @@ + +console.log("User:", "john", "logged in"); diff --git a/recipes/util-print-to-console-log/tests/expected/multiple-imports.cjs b/recipes/util-print-to-console-log/tests/expected/multiple-imports.cjs new file mode 100644 index 00000000..16f7568d --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/multiple-imports.cjs @@ -0,0 +1,5 @@ +const { inspect } = require("node:util"); + +console.log("Starting application"); +const formatted = inspect({ foo: "bar" }); +console.log(formatted); diff --git a/recipes/util-print-to-console-log/tests/expected/named-import-print-only.js b/recipes/util-print-to-console-log/tests/expected/named-import-print-only.js new file mode 100644 index 00000000..402bee3d --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/named-import-print-only.js @@ -0,0 +1,3 @@ + +console.log("Application started"); +console.log("Processing request"); diff --git a/recipes/util-print-to-console-log/tests/expected/print.js b/recipes/util-print-to-console-log/tests/expected/print.js new file mode 100644 index 00000000..d5fe29bc --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/print.js @@ -0,0 +1,2 @@ + +console.log("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/expected/puts.js b/recipes/util-print-to-console-log/tests/expected/puts.js new file mode 100644 index 00000000..d5fe29bc --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/puts.js @@ -0,0 +1,2 @@ + +console.log("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/expected/real-scenario.js b/recipes/util-print-to-console-log/tests/expected/real-scenario.js new file mode 100644 index 00000000..bb7441e3 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/real-scenario.js @@ -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}/`); +}); diff --git a/recipes/util-print-to-console-log/tests/expected/simple-scenario.js b/recipes/util-print-to-console-log/tests/expected/simple-scenario.js new file mode 100644 index 00000000..d5fe29bc --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/simple-scenario.js @@ -0,0 +1,2 @@ + +console.log("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/expected/with-interpolation.js b/recipes/util-print-to-console-log/tests/expected/with-interpolation.js new file mode 100644 index 00000000..bfb1d9d7 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/expected/with-interpolation.js @@ -0,0 +1,3 @@ + +const port = 3000; +console.log(`Server started on port ${port}`); diff --git a/recipes/util-print-to-console-log/tests/input/debug.js b/recipes/util-print-to-console-log/tests/input/debug.js new file mode 100644 index 00000000..c1c5db94 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/debug.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.debug("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/input/error.js b/recipes/util-print-to-console-log/tests/input/error.js new file mode 100644 index 00000000..420871a5 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/error.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.error("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/input/esm-import.mjs b/recipes/util-print-to-console-log/tests/input/esm-import.mjs new file mode 100644 index 00000000..b0fd6cd6 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/esm-import.mjs @@ -0,0 +1,3 @@ +import { print } from "node:util"; + +print("Server listening on port 3000"); diff --git a/recipes/util-print-to-console-log/tests/input/esm-namespace-import.mjs b/recipes/util-print-to-console-log/tests/input/esm-namespace-import.mjs new file mode 100644 index 00000000..7f4209ac --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/esm-namespace-import.mjs @@ -0,0 +1,3 @@ +import * as util from "node:util"; + +util.print("Debug message"); diff --git a/recipes/util-print-to-console-log/tests/input/multiple-arguments.js b/recipes/util-print-to-console-log/tests/input/multiple-arguments.js new file mode 100644 index 00000000..30a602eb --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/multiple-arguments.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.print("User:", "john", "logged in"); diff --git a/recipes/util-print-to-console-log/tests/input/multiple-imports.cjs b/recipes/util-print-to-console-log/tests/input/multiple-imports.cjs new file mode 100644 index 00000000..5660a72a --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/multiple-imports.cjs @@ -0,0 +1,5 @@ +const { print, inspect } = require("node:util"); + +print("Starting application"); +const formatted = inspect({ foo: "bar" }); +console.log(formatted); diff --git a/recipes/util-print-to-console-log/tests/input/named-import-print-only.js b/recipes/util-print-to-console-log/tests/input/named-import-print-only.js new file mode 100644 index 00000000..ffc961e9 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/named-import-print-only.js @@ -0,0 +1,4 @@ +const { print } = require("node:util"); + +print("Application started"); +print("Processing request"); diff --git a/recipes/util-print-to-console-log/tests/input/print.js b/recipes/util-print-to-console-log/tests/input/print.js new file mode 100644 index 00000000..ea0515c3 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/print.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.print("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/input/puts.js b/recipes/util-print-to-console-log/tests/input/puts.js new file mode 100644 index 00000000..6642934b --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/puts.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.puts("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/input/real-scenario.js b/recipes/util-print-to-console-log/tests/input/real-scenario.js new file mode 100644 index 00000000..10094ca9 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/real-scenario.js @@ -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}/`); +}); diff --git a/recipes/util-print-to-console-log/tests/input/simple-scenario.js b/recipes/util-print-to-console-log/tests/input/simple-scenario.js new file mode 100644 index 00000000..ea0515c3 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/simple-scenario.js @@ -0,0 +1,3 @@ +const util = require("node:util"); + +util.print("Hello world"); diff --git a/recipes/util-print-to-console-log/tests/input/with-interpolation.js b/recipes/util-print-to-console-log/tests/input/with-interpolation.js new file mode 100644 index 00000000..a0366ea2 --- /dev/null +++ b/recipes/util-print-to-console-log/tests/input/with-interpolation.js @@ -0,0 +1,4 @@ +const { print } = require("node:util"); + +const port = 3000; +print(`Server started on port ${port}`); diff --git a/recipes/util-print-to-console-log/tests/named-import-print-only.js b/recipes/util-print-to-console-log/tests/named-import-print-only.js new file mode 100644 index 00000000..6e7e080c --- /dev/null +++ b/recipes/util-print-to-console-log/tests/named-import-print-only.js @@ -0,0 +1,3 @@ + +console.log("Application started"); +console.log("Processing request"); \ No newline at end of file diff --git a/recipes/util-print-to-console-log/workflow.yaml b/recipes/util-print-to-console-log/workflow.yaml new file mode 100644 index 00000000..6c6274e3 --- /dev/null +++ b/recipes/util-print-to-console-log/workflow.yaml @@ -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 From a59787eaecbcab2989c31f4b9ed740084eb77a32 Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 13:34:08 +0100 Subject: [PATCH 2/7] feat(util/remove-lines): remove duplicated instructions before process --- utils/src/ast-grep/remove-lines.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/utils/src/ast-grep/remove-lines.ts b/utils/src/ast-grep/remove-lines.ts index 052fbcf4..0f806cb0 100644 --- a/utils/src/ast-grep/remove-lines.ts +++ b/utils/src/ast-grep/remove-lines.ts @@ -3,6 +3,17 @@ import type { Range } from "@codemod.com/jssg-types/main"; export function removeLines(sourceCode: string, ranges: Range[]) { const lines = sourceCode.split("\n"); + const uniqueRemoves = new Map(); + + for (const range of ranges) { + const key = `start-line:${range.start.line},end-line:${range.end.line},start-column:${range.start.column},end-column:${range.end.column}`; + if (!uniqueRemoves.has(key)) { + uniqueRemoves.set(key, range); + } + } + + ranges = Array.from(uniqueRemoves.values()); + let removeCounter = 0; for (const range of ranges) { const start = range.start.line - removeCounter; From 5074b68c2382e0838b701d1b74669a706b4d9180 Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 13:34:58 +0100 Subject: [PATCH 3/7] feat(util-print-to-console.log): update package-lock.json --- package-lock.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index c45410cf..25821dbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1440,6 +1440,10 @@ "resolved": "recipes/util-log-to-console-log", "link": true }, + "node_modules/@nodejs/util-print-to-console-log": { + "resolved": "recipes/util-print-to-console-log", + "link": true + }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -4128,6 +4132,17 @@ "@codemod.com/jssg-types": "^1.0.3" } }, + "recipes/util-print-to-console-log": { + "name": "@nodejs/util-print-to-console-log", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "utils": { "name": "@nodejs/codemod-utils", "version": "0.0.0", From eccba8d3337604bf27ce088fcf5f85b2ed625cf6 Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 13:37:51 +0100 Subject: [PATCH 4/7] feat(util-print-to-console.log): update docs/tsdocs --- recipes/util-print-to-console-log/README.md | 2 +- recipes/util-print-to-console-log/src/workflow.ts | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/recipes/util-print-to-console-log/README.md b/recipes/util-print-to-console-log/README.md index f2ecc2c3..c0e2f517 100644 --- a/recipes/util-print-to-console-log/README.md +++ b/recipes/util-print-to-console-log/README.md @@ -1,4 +1,4 @@ -# `util.print, util.puts, util.debug, util.error` DEP0059,DEP0026,DEP0027,DEP0028,DEP0029 +# `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()`. diff --git a/recipes/util-print-to-console-log/src/workflow.ts b/recipes/util-print-to-console-log/src/workflow.ts index b0dcf043..b0ccd038 100644 --- a/recipes/util-print-to-console-log/src/workflow.ts +++ b/recipes/util-print-to-console-log/src/workflow.ts @@ -40,13 +40,16 @@ const updates = [ ]; /* - * Transforms `util.log($$$ARG)` usage to `console.log(new Date().toLocaleString(), $$$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.log imports, noting the replacement rule, import node, and binding name. + * 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.log($$$ARG) with the new console.log format, + * 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. @@ -67,7 +70,7 @@ export default function transform(root: SgRoot): string | null { for (const update of updates) { const bind = resolveBindingPath(node, update.oldBind); - // if `log` function ins't coming from `node:util` + // if `fn` function ins't coming from `node:util` if (!bind) continue; bindsToReplace.push({ From 1f48ef9eb3f3c54c2b963e007a136a5d21605e40 Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 13:38:45 +0100 Subject: [PATCH 5/7] chore(util-print-to-console.log): remove unused test --- .../util-print-to-console-log/tests/named-import-print-only.js | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 recipes/util-print-to-console-log/tests/named-import-print-only.js diff --git a/recipes/util-print-to-console-log/tests/named-import-print-only.js b/recipes/util-print-to-console-log/tests/named-import-print-only.js deleted file mode 100644 index 6e7e080c..00000000 --- a/recipes/util-print-to-console-log/tests/named-import-print-only.js +++ /dev/null @@ -1,3 +0,0 @@ - -console.log("Application started"); -console.log("Processing request"); \ No newline at end of file From 53f11242ad6e4d1cdb32244f0f22de7f3a1ead0c Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Wed, 20 Aug 2025 18:15:39 -0300 Subject: [PATCH 6/7] Update recipes/util-print-to-console-log/package.json Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- recipes/util-print-to-console-log/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/util-print-to-console-log/package.json b/recipes/util-print-to-console-log/package.json index fdd03d92..67da2e7b 100644 --- a/recipes/util-print-to-console-log/package.json +++ b/recipes/util-print-to-console-log/package.json @@ -4,7 +4,7 @@ "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 --update-snapshots -l typescript ./src/workflow.ts ./" + "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./" }, "repository": { "type": "git", From 506959e1a6d9acea0966391b8f5a2dd52bc42d5e Mon Sep 17 00:00:00 2001 From: Bruno Rodrigues Date: Fri, 22 Aug 2025 18:33:48 +0100 Subject: [PATCH 7/7] fix(util/remove-lines): add comment and test to check behaviour when have multiple equal ranges --- utils/src/ast-grep/remove-lines.test.ts | 31 +++++++++++++++++++++++++ utils/src/ast-grep/remove-lines.ts | 2 ++ 2 files changed, 33 insertions(+) diff --git a/utils/src/ast-grep/remove-lines.test.ts b/utils/src/ast-grep/remove-lines.test.ts index 0109ec57..3951380f 100644 --- a/utils/src/ast-grep/remove-lines.test.ts +++ b/utils/src/ast-grep/remove-lines.test.ts @@ -124,4 +124,35 @@ describe("remove-lines", () => { assert.strictEqual(expected, output); }); + + it("Should remove the correct lines when receiving duplicated ranges to remove", () => { + const code = dedent` + line 1 + line 2 + line 3 + line 4 + `; + + const expected = dedent` + line 1 + line 4 + `; + + const range: Range = { + start: { + line: 1, + column: 0, + index: 0, + }, + end: { + line: 2, + column: 0, + index: 0, + }, + }; + + const output = removeLines(code, [range, range, range, range, range]); + + assert.strictEqual(expected, output); + }); }); diff --git a/utils/src/ast-grep/remove-lines.ts b/utils/src/ast-grep/remove-lines.ts index 0f806cb0..4a693b4d 100644 --- a/utils/src/ast-grep/remove-lines.ts +++ b/utils/src/ast-grep/remove-lines.ts @@ -3,6 +3,8 @@ import type { Range } from "@codemod.com/jssg-types/main"; export function removeLines(sourceCode: string, ranges: Range[]) { const lines = sourceCode.split("\n"); + // Remove duplicate ranges to prevent attempting to delete the same range multiple times, + // which could cause inconsistent behavior const uniqueRemoves = new Map(); for (const range of ranges) {