From f6f823e4b3c83da34aeb961d6010856f0d797df2 Mon Sep 17 00:00:00 2001 From: nekojanai Date: Wed, 10 Sep 2025 17:04:19 +0200 Subject: [PATCH 1/7] feat(migrate-legacy-buffer-atob-btoa): introduce --- recipes/buffer-atob-btoa/README.md | 42 +++++++++++++++ recipes/buffer-atob-btoa/codemod.yaml | 21 ++++++++ recipes/buffer-atob-btoa/package.json | 21 ++++++++ recipes/buffer-atob-btoa/src/workflow.ts | 51 +++++++++++++++++++ .../tests/expected/file-00.js | 3 ++ .../tests/expected/file-01.js | 3 ++ .../buffer-atob-btoa/tests/input/file-00.js | 4 ++ .../buffer-atob-btoa/tests/input/file-01.js | 4 ++ recipes/buffer-atob-btoa/workflow.yaml | 27 ++++++++++ 9 files changed, 176 insertions(+) create mode 100644 recipes/buffer-atob-btoa/README.md create mode 100644 recipes/buffer-atob-btoa/codemod.yaml create mode 100644 recipes/buffer-atob-btoa/package.json create mode 100644 recipes/buffer-atob-btoa/src/workflow.ts create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-00.js create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-01.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-00.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-01.js create mode 100644 recipes/buffer-atob-btoa/workflow.yaml diff --git a/recipes/buffer-atob-btoa/README.md b/recipes/buffer-atob-btoa/README.md new file mode 100644 index 00000000..47b06355 --- /dev/null +++ b/recipes/buffer-atob-btoa/README.md @@ -0,0 +1,42 @@ +# Migrate legacy `buffer.atob()` and `buffer.btoa()` APIs + +Migrates usage of the legacy APIs `buffer.atob()` and `buffer.btoa()` to the current recommended approaches. + +## Example + +### Migrating buffer.atob(data) + +**Before:** +```js +const buffer = require('node:buffer'); +const data = 'SGVsbG8gV29ybGQh'; // "Hello World!" in base64 +const decodedData = buffer.atob(data); +console.log(decodedData); // Outputs: Hello World! +``` + +**After:** +```js +const data = 'SGVsbG8gV29ybGQh'; // "Hello World!" in base64 +const decodedData = Buffer.from(data, 'base64').toString('binary'); +console.log(decodedData); // Outputs: Hello World! +``` + +### Migrating buffer.btoa(data) + +**Before:** +```js +const buffer = require('node:buffer'); +const data = 'Hello World!'; +const encodedData = buffer.btoa(data); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh +``` + +**After:** +```js +const data = 'Hello World!'; +const encodedData = Buffer.from(data, 'binary').toString('base64'); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh +``` + +## REFS +* [Node.js Documentation: Buffer](https://nodejs.org/api/buffer.html) \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/codemod.yaml b/recipes/buffer-atob-btoa/codemod.yaml new file mode 100644 index 00000000..cb501dc9 --- /dev/null +++ b/recipes/buffer-atob-btoa/codemod.yaml @@ -0,0 +1,21 @@ +schema_version: "1.0" +name: "@nodejs/buffer-atob-btoa" +version: "1.0.0" +description: Migrates usage of the legacy APIs `buffer.atob()` and `buffer.btoa()` to the current recommended approaches +author: nekojanai (Jana) +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + +registry: + access: public + visibility: public diff --git a/recipes/buffer-atob-btoa/package.json b/recipes/buffer-atob-btoa/package.json new file mode 100644 index 00000000..071a4b32 --- /dev/null +++ b/recipes/buffer-atob-btoa/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nodejs/buffer-atob-btoa", + "version": "1.0.0", + "description": "Migrates usage of the legacy APIs `buffer.atob()` and `buffer.btoa()` to the current recommended approaches", + "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/buffer-atob-btoa", + "bugs": "https://github.com/nodejs/userland-migrations/issues" + }, + "author": "nekojanai (Jana)", + "license": "MIT", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/buffer-atob-btoa/README.md", + "dependencies": { + "@nodejs/codemod-utils": "*" + } +} \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts new file mode 100644 index 00000000..da491cdf --- /dev/null +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -0,0 +1,51 @@ +import type { Edit, Range, SgRoot } from '@codemod.com/jssg-types/main'; +import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; +import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root() + const edits: Edit[] = []; + const linesToRemove: Range[] = []; + + // @ts-expect-error - ast-grep types are not fully compatible with JSSG types + const requireStatements = getNodeRequireCalls(root, 'buffer'); + const atobFunctionCalls = rootNode.findAll({ + rule: { + pattern: `buffer.atob($ARG)` + } + }); + + // Remove all buffer require statements + for (const statement of requireStatements) { + linesToRemove.push(statement.range()); + } + + // Rewrite atob function calls + for (const call of atobFunctionCalls) { + const argMatch = call.getMatch("ARG"); + if (argMatch) { + const arg = argMatch.text(); + const replacement = `Buffer.from(${arg}, 'base64').toString('binary')`; + edits.push(call.replace(replacement)); + } + } + + const btoaFunctionCalls = rootNode.findAll({ + rule: { + pattern: `buffer.btoa($ARG)` + } + }); + + // Rewrite btoa function calls + for (const call of btoaFunctionCalls) { + const argMatch = call.getMatch("ARG"); + if (argMatch) { + const arg = argMatch.text(); + const replacement = `Buffer.from(${arg}, 'binary').toString('base64')`; + edits.push(call.replace(replacement)); + } + } + + return removeLines(rootNode.commitEdits(edits), linesToRemove); +} \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/expected/file-00.js b/recipes/buffer-atob-btoa/tests/expected/file-00.js new file mode 100644 index 00000000..fd9cf97f --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-00.js @@ -0,0 +1,3 @@ +const data = 'SGVsbG8gV29ybGQh'; // "Hello World!" in base64 +const decodedData = Buffer.from(data, 'base64').toString('binary'); +console.log(decodedData); // Outputs: Hello World! \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/expected/file-01.js b/recipes/buffer-atob-btoa/tests/expected/file-01.js new file mode 100644 index 00000000..62d152d2 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-01.js @@ -0,0 +1,3 @@ +const data = 'Hello World!'; +const encodedData = Buffer.from(data, 'binary').toString('base64'); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-00.js b/recipes/buffer-atob-btoa/tests/input/file-00.js new file mode 100644 index 00000000..8ca779f7 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-00.js @@ -0,0 +1,4 @@ +const buffer = require('node:buffer'); +const data = 'SGVsbG8gV29ybGQh'; // "Hello World!" in base64 +const decodedData = buffer.atob(data); +console.log(decodedData); // Outputs: Hello World! \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-01.js b/recipes/buffer-atob-btoa/tests/input/file-01.js new file mode 100644 index 00000000..179ec7a5 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-01.js @@ -0,0 +1,4 @@ +const buffer = require('node:buffer'); +const data = 'Hello World!'; +const encodedData = buffer.btoa(data); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/workflow.yaml b/recipes/buffer-atob-btoa/workflow.yaml new file mode 100644 index 00000000..d71f6e03 --- /dev/null +++ b/recipes/buffer-atob-btoa/workflow.yaml @@ -0,0 +1,27 @@ +# 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 + runtime: + type: direct + steps: + - name: Migrates usage of the legacy APIs `buffer.atob()` and `buffer.btoa()` to the current recommended approaches + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript From 5b5ab6371ba3c8e18aabc9d35d10078f4a55d79a Mon Sep 17 00:00:00 2001 From: nekojanai Date: Wed, 17 Sep 2025 18:40:02 +0200 Subject: [PATCH 2/7] chore(package-lock.json): add recipe --- package-lock.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index df0d2833..39fe7973 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1408,6 +1408,10 @@ "node": ">=22 || ^20.6.0 || ^18.19.0" } }, + "node_modules/@nodejs/buffer-atob-btoa": { + "resolved": "recipes/buffer-atob-btoa", + "link": true + }, "node_modules/@nodejs/codemod-utils": { "resolved": "utils", "link": true @@ -4168,6 +4172,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "recipes/buffer-atob-btoa": { + "name": "@nodejs/buffer-atob-btoa", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + } + }, "recipes/correct-ts-specifiers": { "name": "@nodejs/correct-ts-specifiers", "version": "1.0.0", From 6660cc277cfb76a022938a012f39da86f40e4bbf Mon Sep 17 00:00:00 2001 From: nekojanai Date: Wed, 17 Sep 2025 18:42:32 +0200 Subject: [PATCH 3/7] chore(workflow.ts): replace template literals with string literals --- recipes/buffer-atob-btoa/src/workflow.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts index da491cdf..d8e2ffde 100644 --- a/recipes/buffer-atob-btoa/src/workflow.ts +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -12,7 +12,7 @@ export default function transform(root: SgRoot): string | null { const requireStatements = getNodeRequireCalls(root, 'buffer'); const atobFunctionCalls = rootNode.findAll({ rule: { - pattern: `buffer.atob($ARG)` + pattern: 'buffer.atob($ARG)' } }); @@ -33,7 +33,7 @@ export default function transform(root: SgRoot): string | null { const btoaFunctionCalls = rootNode.findAll({ rule: { - pattern: `buffer.btoa($ARG)` + pattern: 'buffer.btoa($ARG)' } }); From 5137bdf11d571e0f650cf274a4efb75d70b039cf Mon Sep 17 00:00:00 2001 From: nekojanai Date: Sun, 28 Sep 2025 17:23:14 +0200 Subject: [PATCH 4/7] chore(workflow.ts): handle dynamic and esm imports --- recipes/buffer-atob-btoa/src/workflow.ts | 8 +++++++- recipes/buffer-atob-btoa/tests/expected/file-02.js | 3 +++ recipes/buffer-atob-btoa/tests/expected/file-03.js | 3 +++ recipes/buffer-atob-btoa/tests/input/file-02.js | 4 ++++ recipes/buffer-atob-btoa/tests/input/file-03.js | 4 ++++ 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-02.js create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-03.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-02.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-03.js diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts index d8e2ffde..12b79af0 100644 --- a/recipes/buffer-atob-btoa/src/workflow.ts +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -1,6 +1,7 @@ import type { Edit, Range, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; +import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; export default function transform(root: SgRoot): string | null { @@ -8,8 +9,8 @@ export default function transform(root: SgRoot): string | null { const edits: Edit[] = []; const linesToRemove: Range[] = []; - // @ts-expect-error - ast-grep types are not fully compatible with JSSG types const requireStatements = getNodeRequireCalls(root, 'buffer'); + const importStatements = getNodeImportStatements(root, 'buffer'); const atobFunctionCalls = rootNode.findAll({ rule: { pattern: 'buffer.atob($ARG)' @@ -21,6 +22,11 @@ export default function transform(root: SgRoot): string | null { linesToRemove.push(statement.range()); } + // Remove all buffer import statements + for (const statement of importStatements) { + linesToRemove.push(statement.range()); + } + // Rewrite atob function calls for (const call of atobFunctionCalls) { const argMatch = call.getMatch("ARG"); diff --git a/recipes/buffer-atob-btoa/tests/expected/file-02.js b/recipes/buffer-atob-btoa/tests/expected/file-02.js new file mode 100644 index 00000000..62d152d2 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-02.js @@ -0,0 +1,3 @@ +const data = 'Hello World!'; +const encodedData = Buffer.from(data, 'binary').toString('base64'); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/expected/file-03.js b/recipes/buffer-atob-btoa/tests/expected/file-03.js new file mode 100644 index 00000000..62d152d2 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-03.js @@ -0,0 +1,3 @@ +const data = 'Hello World!'; +const encodedData = Buffer.from(data, 'binary').toString('base64'); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-02.js b/recipes/buffer-atob-btoa/tests/input/file-02.js new file mode 100644 index 00000000..6ed1e665 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-02.js @@ -0,0 +1,4 @@ +import buffer from "node:buffer"; +const data = 'Hello World!'; +const encodedData = buffer.btoa(data); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-03.js b/recipes/buffer-atob-btoa/tests/input/file-03.js new file mode 100644 index 00000000..4a195ce9 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-03.js @@ -0,0 +1,4 @@ +const buffer = require("node:buffer"); +const data = 'Hello World!'; +const encodedData = buffer.btoa(data); +console.log(encodedData); // Outputs: SGVsbG8gV29ybGQh \ No newline at end of file From b6a2bd597c6fcabf01e9aff02a881e499fefa212 Mon Sep 17 00:00:00 2001 From: nekojanai Date: Tue, 30 Sep 2025 21:35:15 +0200 Subject: [PATCH 5/7] chore(workflow.ts): improve handle dynamic and esm imports --- recipes/buffer-atob-btoa/src/workflow.ts | 124 +++++++++++------- .../tests/expected/file-04.js | 4 + .../tests/expected/file-05.js | 4 + .../buffer-atob-btoa/tests/input/file-04.js | 4 + .../buffer-atob-btoa/tests/input/file-05.js | 4 + 5 files changed, 89 insertions(+), 51 deletions(-) create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-04.js create mode 100644 recipes/buffer-atob-btoa/tests/expected/file-05.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-04.js create mode 100644 recipes/buffer-atob-btoa/tests/input/file-05.js diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts index 12b79af0..45bc88f9 100644 --- a/recipes/buffer-atob-btoa/src/workflow.ts +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -1,57 +1,79 @@ -import type { Edit, Range, SgRoot } from '@codemod.com/jssg-types/main'; +import type { Edit, Kinds, Range, SgNode, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; +import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; export default function transform(root: SgRoot): string | null { - const rootNode = root.root() - const edits: Edit[] = []; - const linesToRemove: Range[] = []; - - const requireStatements = getNodeRequireCalls(root, 'buffer'); - const importStatements = getNodeImportStatements(root, 'buffer'); - const atobFunctionCalls = rootNode.findAll({ - rule: { - pattern: 'buffer.atob($ARG)' - } - }); - - // Remove all buffer require statements - for (const statement of requireStatements) { - linesToRemove.push(statement.range()); - } - - // Remove all buffer import statements - for (const statement of importStatements) { - linesToRemove.push(statement.range()); - } - - // Rewrite atob function calls - for (const call of atobFunctionCalls) { - const argMatch = call.getMatch("ARG"); - if (argMatch) { - const arg = argMatch.text(); - const replacement = `Buffer.from(${arg}, 'base64').toString('binary')`; - edits.push(call.replace(replacement)); - } - } - - const btoaFunctionCalls = rootNode.findAll({ - rule: { - pattern: 'buffer.btoa($ARG)' - } - }); - - // Rewrite btoa function calls - for (const call of btoaFunctionCalls) { - const argMatch = call.getMatch("ARG"); - if (argMatch) { - const arg = argMatch.text(); - const replacement = `Buffer.from(${arg}, 'binary').toString('base64')`; - edits.push(call.replace(replacement)); - } - } - - return removeLines(rootNode.commitEdits(edits), linesToRemove); -} \ No newline at end of file + const rootNode = root.root(); + const bindingStatementFnTuples: [string, SgNode>, (arg: string) => string][] = []; + const edits: Edit[] = []; + const linesToRemove: Range[] = []; + + const updates = [ + { + oldBind: "$.atob", + replaceFn: (arg: string) => `Buffer.from(${arg}, 'base64').toString('binary')` + }, + { + oldBind: "$.btoa", + replaceFn: (arg: string) => `Buffer.from(${arg}, 'binary').toString('base64')` + } + ]; + + const statements = [...getNodeRequireCalls(root, 'buffer'), ...getNodeImportStatements(root, 'buffer')]; + + for (const statement of statements) { + for (const update of updates) { + const binding = resolveBindingPath(statement, update.oldBind); + if (binding) bindingStatementFnTuples.push([binding, statement, update.replaceFn]); + } + } + + for (const [binding, statement, fn] of bindingStatementFnTuples) { + + const result = removeBinding(statement, binding); + + if (result?.edit) edits.push(result.edit); + if (result?.lineToRemove) linesToRemove.push(result.lineToRemove); + + const calls = rootNode.findAll({ + rule: { + pattern: `${binding}($ARG)` + } + }); + + const otherCalls = rootNode.findAll({ + rule: { + all: [ + { + pattern: 'buffer.$FN' + }, + { + not: { + pattern: '$.btoa($ARG)' + } + }, + { + not: { + pattern: '$.atob($ARG)' + } + } + ] + } + }); + + for (const call of calls) { + const argMatch = call.getMatch("ARG"); + if (argMatch) edits.push(call.replace(fn(argMatch.text()))); + } + + if (calls.length === otherCalls.length) { + linesToRemove.push(statement.range()); + } + } + + return removeLines(rootNode.commitEdits(edits), linesToRemove); +} diff --git a/recipes/buffer-atob-btoa/tests/expected/file-04.js b/recipes/buffer-atob-btoa/tests/expected/file-04.js new file mode 100644 index 00000000..a358057d --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-04.js @@ -0,0 +1,4 @@ +import buffer from "node:buffer"; +buffer.constants.MAX_LENGTH; +const data = 'Hello World!'; +Buffer.from(data, 'binary').toString('base64'); \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/expected/file-05.js b/recipes/buffer-atob-btoa/tests/expected/file-05.js new file mode 100644 index 00000000..7cb29f7a --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/expected/file-05.js @@ -0,0 +1,4 @@ +import { isUtf8 } from "node:buffer"; +const data = 'Hello World!'; +isUtf8(data); +Buffer.from(data, 'base64').toString('binary'); \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-04.js b/recipes/buffer-atob-btoa/tests/input/file-04.js new file mode 100644 index 00000000..0d33acc1 --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-04.js @@ -0,0 +1,4 @@ +import buffer from "node:buffer"; +buffer.constants.MAX_LENGTH; +const data = 'Hello World!'; +buffer.btoa(data); \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/tests/input/file-05.js b/recipes/buffer-atob-btoa/tests/input/file-05.js new file mode 100644 index 00000000..ff65cf2a --- /dev/null +++ b/recipes/buffer-atob-btoa/tests/input/file-05.js @@ -0,0 +1,4 @@ +import { atob, isUtf8 } from "node:buffer"; +const data = 'Hello World!'; +isUtf8(data); +atob(data); \ No newline at end of file From e02d5312c548fce61879e32500ffdf86b7ace69b Mon Sep 17 00:00:00 2001 From: nekojanai Date: Mon, 13 Oct 2025 00:07:54 +0200 Subject: [PATCH 6/7] chore(workflow.ts): add comment and empty edits check --- package-lock.json | 3 +++ recipes/buffer-atob-btoa/package.json | 3 +++ recipes/buffer-atob-btoa/src/workflow.ts | 13 +++++++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b56a131..4b4f59c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4235,6 +4235,9 @@ "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.9" } }, "recipes/correct-ts-specifiers": { diff --git a/recipes/buffer-atob-btoa/package.json b/recipes/buffer-atob-btoa/package.json index 071a4b32..d6d01781 100644 --- a/recipes/buffer-atob-btoa/package.json +++ b/recipes/buffer-atob-btoa/package.json @@ -17,5 +17,8 @@ "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/buffer-atob-btoa/README.md", "dependencies": { "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.9" } } \ No newline at end of file diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts index 45bc88f9..d5fe15b9 100644 --- a/recipes/buffer-atob-btoa/src/workflow.ts +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -8,7 +8,7 @@ import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; export default function transform(root: SgRoot): string | null { const rootNode = root.root(); - const bindingStatementFnTuples: [string, SgNode>, (arg: string) => string][] = []; + const bindingStatementFnTuples: [string, SgNode, (arg: string) => string][] = []; const edits: Edit[] = []; const linesToRemove: Range[] = []; @@ -23,7 +23,10 @@ export default function transform(root: SgRoot): string | null { } ]; - const statements = [...getNodeRequireCalls(root, 'buffer'), ...getNodeImportStatements(root, 'buffer')]; + const statements = [ + ...getNodeRequireCalls(root, 'buffer'), + ...getNodeImportStatements(root, 'buffer') + ]; for (const statement of statements) { for (const update of updates) { @@ -38,13 +41,13 @@ export default function transform(root: SgRoot): string | null { if (result?.edit) edits.push(result.edit); if (result?.lineToRemove) linesToRemove.push(result.lineToRemove); - + // Check for calls to the specified binding const calls = rootNode.findAll({ rule: { pattern: `${binding}($ARG)` } }); - + // Check for any other calls, so as to not remove a statement that is still being used const otherCalls = rootNode.findAll({ rule: { all: [ @@ -75,5 +78,7 @@ export default function transform(root: SgRoot): string | null { } } + if (!edits.length) return null; + return removeLines(rootNode.commitEdits(edits), linesToRemove); } From 551fbb137c8dd9f1e5d21972c98c71571e49b253 Mon Sep 17 00:00:00 2001 From: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:13:05 -0400 Subject: [PATCH 7/7] fixup!: apply suggestions from code review --- recipes/buffer-atob-btoa/codemod.yaml | 1 + recipes/buffer-atob-btoa/src/workflow.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/buffer-atob-btoa/codemod.yaml b/recipes/buffer-atob-btoa/codemod.yaml index cb501dc9..079ea179 100644 --- a/recipes/buffer-atob-btoa/codemod.yaml +++ b/recipes/buffer-atob-btoa/codemod.yaml @@ -15,6 +15,7 @@ targets: keywords: - transformation - migration + - buffers registry: access: public diff --git a/recipes/buffer-atob-btoa/src/workflow.ts b/recipes/buffer-atob-btoa/src/workflow.ts index d5fe15b9..c589296a 100644 --- a/recipes/buffer-atob-btoa/src/workflow.ts +++ b/recipes/buffer-atob-btoa/src/workflow.ts @@ -36,7 +36,6 @@ export default function transform(root: SgRoot): string | null { } for (const [binding, statement, fn] of bindingStatementFnTuples) { - const result = removeBinding(statement, binding); if (result?.edit) edits.push(result.edit);