Skip to content
Open
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.

27 changes: 27 additions & 0 deletions recipes/err-invalid-callback/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# DEP0159: `ERR_INVALID_CALLBACK` replaced by `ERR_INVALID_ARG_TYPE`

This recipe replaces references to the deprecated `ERR_INVALID_CALLBACK` error code with `ERR_INVALID_ARG_TYPE`.

See [DEP0159](https://nodejs.org/api/deprecations.html#DEP0159).

## Example

```diff
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
- if (err.code === "ERR_INVALID_CALLBACK") {
+ if (err.code === "ERR_INVALID_ARG_TYPE") {
console.error("Invalid callback provided");
}
}
```

Also handles deduplication when both codes were already checked:

```diff
const isCallbackError =
- err.code === "ERR_INVALID_CALLBACK" ||
- err.code === "ERR_INVALID_ARG_TYPE";
+ err.code === "ERR_INVALID_ARG_TYPE";
```
23 changes: 23 additions & 0 deletions recipes/err-invalid-callback/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
schema_version: "1.0"
name: "@nodejs/err-invalid-callback"
version: "1.0.0"
description: Handle DEP0159 by replacing ERR_INVALID_CALLBACK with ERR_INVALID_ARG_TYPE.
author: Stanley Shen
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration
- errors
- DEP0159

registry:
access: public
visibility: public
24 changes: 24 additions & 0 deletions recipes/err-invalid-callback/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@nodejs/err-invalid-callback",
"version": "1.0.0",
"description": "Handle DEP0159 by replacing ERR_INVALID_CALLBACK with ERR_INVALID_ARG_TYPE.",
"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/err-invalid-callback",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "Stanley Shen",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/err-invalid-callback/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.5.0"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
95 changes: 95 additions & 0 deletions recipes/err-invalid-callback/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Edit, SgRoot } from '@codemod.com/jssg-types/main';
import type JS from '@codemod.com/jssg-types/langs/javascript';

const OLD_CODE = 'ERR_INVALID_CALLBACK';
const NEW_CODE = 'ERR_INVALID_ARG_TYPE';

/**
* Transform function that replaces references to the deprecated
* ERR_INVALID_CALLBACK error code with ERR_INVALID_ARG_TYPE.
*
* See DEP0159: https://nodejs.org/api/deprecations.html#DEP0159
*
* Only matches string literals in error-code-related contexts:
* - Binary comparisons: err.code === "ERR_INVALID_CALLBACK"
* - Object properties: { code: "ERR_INVALID_CALLBACK" }
* - Switch cases: case "ERR_INVALID_CALLBACK":
* - String matching calls: .includes("ERR_INVALID_CALLBACK")
*
* Does NOT match strings used in non-error-code contexts such as
* console.warn("ERR_INVALID_CALLBACK") or throw new Error("ERR_INVALID_CALLBACK").
*
* Deduplicates redundant checks after replacement (e.g., a === "X" || a === "X").
*/
export default function transform(root: SgRoot<JS>): string | null {
const rootNode = root.root();
const edits: Edit[] = [];

// Match exact string fragments only in error-code-related AST contexts
const stringFragments = rootNode.findAll({
rule: {
kind: 'string_fragment',
regex: `^${OLD_CODE}$`,
inside: {
kind: 'string',
any: [
// err.code === "ERR_INVALID_CALLBACK"
{ inside: { kind: 'binary_expression' } },
// { code: "ERR_INVALID_CALLBACK" }
{ inside: { kind: 'pair' } },
// case "ERR_INVALID_CALLBACK":
{ inside: { kind: 'switch_case' } },
// .includes("ERR_INVALID_CALLBACK"), .indexOf("ERR_INVALID_CALLBACK"), etc.
{
inside: {
kind: 'arguments',
inside: {
kind: 'call_expression',
has: {
kind: 'member_expression',
has: {
kind: 'property_identifier',
regex: '^(includes|indexOf|match|test|startsWith|endsWith)$',
},
},
},
},
},
],
},
},
});

for (const fragment of stringFragments) {
edits.push(fragment.replace(NEW_CODE));
}

if (!edits.length) return null;

let result = rootNode.commitEdits(edits);

// Post-process: remove duplicate conditions after replacement
// e.g., `err.code === "ERR_INVALID_ARG_TYPE" || \n err.code === "ERR_INVALID_ARG_TYPE"`
// becomes `err.code === "ERR_INVALID_ARG_TYPE"`
result = deduplicateBinaryExpressions(result);

return result;
}

/**
* Remove duplicate operands in || expressions that arise from the replacement.
*
* After replacing ERR_INVALID_CALLBACK → ERR_INVALID_ARG_TYPE, code that previously
* checked for both codes (e.g., `a === "ERR_INVALID_CALLBACK" || a === "ERR_INVALID_ARG_TYPE"`)
* will have two identical conditions that should be collapsed into one.
*
* The regex captures a `<lhs> === <quote>ERR_INVALID_ARG_TYPE<quote>` expression,
* then matches `|| <same expression>`. The lhs is captured with [\w.[\]"']+ to
* support property access patterns like `err.code`, `err["code"]`, and simple identifiers.
*/
function deduplicateBinaryExpressions(code: string): string {
return code.replace(
/([\w.[\]"']+\s*===\s*["']ERR_INVALID_ARG_TYPE["'])\s*\|\|\s*\n?\s*\1/g,
'$1',
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const assert = require("node:assert");

assert.throws(
() => fs.readFile("file.txt", 123),
{ code: "ERR_INVALID_ARG_TYPE" }
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const assert = require("node:assert");

assert.throws(
() => fs.readFile("file.txt", 123),
{ code: "ERR_INVALID_CALLBACK" }
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const assert = require("node:assert");

assert.throws(
() => fs.readFile("file.txt", 123),
{ code: "ERR_INVALID_ARG_TYPE" }
);
9 changes: 9 additions & 0 deletions recipes/err-invalid-callback/tests/deduplication/expected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
const isCallbackError =
err.code === "ERR_INVALID_ARG_TYPE";
if (isCallbackError) {
// Handle invalid callback error
}
}
10 changes: 10 additions & 0 deletions recipes/err-invalid-callback/tests/deduplication/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
const isCallbackError =
err.code === "ERR_INVALID_CALLBACK" ||
err.code === "ERR_INVALID_ARG_TYPE";
if (isCallbackError) {
// Handle invalid callback error
}
}
9 changes: 9 additions & 0 deletions recipes/err-invalid-callback/tests/deduplication/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
const isCallbackError =
err.code === "ERR_INVALID_ARG_TYPE";
if (isCallbackError) {
// Handle invalid callback error
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const myUtility = (file) => {
// do something

file.method();

if (file.empty) {
console.warn("ERR_INVALID_CALLBACK");
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const myUtility = (file) => {
// do something

file.method();

if (file.empty) {
console.warn("ERR_INVALID_CALLBACK");
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const myUtility = (file) => {
// do something

file.method();

if (file.empty) {
console.warn("ERR_INVALID_CALLBACK");
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === "ERR_INVALID_ARG_TYPE") {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === "ERR_INVALID_CALLBACK") {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === "ERR_INVALID_ARG_TYPE") {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// No ERR_INVALID_CALLBACK references - should not be modified
try {
fs.readFile("file.txt", callback);
} catch (err) {
if (err.code === "ENOENT") {
console.error("File not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// No ERR_INVALID_CALLBACK references - should not be modified
try {
fs.readFile("file.txt", callback);
} catch (err) {
if (err.code === "ENOENT") {
console.error("File not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// No ERR_INVALID_CALLBACK references - should not be modified
try {
fs.readFile("file.txt", callback);
} catch (err) {
if (err.code === "ENOENT") {
console.error("File not found");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Single-quoted string usage
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === 'ERR_INVALID_ARG_TYPE') {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Single-quoted string usage
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === 'ERR_INVALID_CALLBACK') {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Single-quoted string usage
try {
fs.readFile("file.txt", "invalid-callback");
} catch (err) {
if (err.code === 'ERR_INVALID_ARG_TYPE') {
console.error("Invalid callback provided");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (err.toString().includes("ERR_INVALID_ARG_TYPE")) {
// Handle callback error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (err.toString().includes("ERR_INVALID_CALLBACK")) {
// Handle callback error
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (err.toString().includes("ERR_INVALID_ARG_TYPE")) {
// Handle callback error
}
8 changes: 8 additions & 0 deletions recipes/err-invalid-callback/tests/switch-case/expected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
switch (error.code) {
case "ERR_INVALID_ARG_TYPE":
console.log("Invalid callback");
break;
case "ENOENT":
console.log("File not found");
break;
}
8 changes: 8 additions & 0 deletions recipes/err-invalid-callback/tests/switch-case/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
switch (error.code) {
case "ERR_INVALID_CALLBACK":
console.log("Invalid callback");
break;
case "ENOENT":
console.log("File not found");
break;
}
Loading