-
-
Notifications
You must be signed in to change notification settings - Fork 17
feat(types-is-native-error): introduce #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
01ab9ae
2587dea
d519ac2
ab5402e
3a06a50
33b88aa
0a4f916
6745525
aac2ddf
bf7569b
27249c8
06e21bb
61d525e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# `types.isNativeError` DEP0197 | ||
|
||
This recipe transforms the usage of `types.isNativeError` to use the `Error.isError`. | ||
|
||
See [DEP0197](https://nodejs.org/api/deprecations.html#DEP0197). | ||
|
||
## Example | ||
|
||
**Before:** | ||
|
||
```js | ||
import { types } from "node:util"; | ||
|
||
if (types.isNativeError(err)) { | ||
// handle the error | ||
} | ||
``` | ||
|
||
**After:** | ||
|
||
```js | ||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
schema_version: "1.0" | ||
name: "@nodejs/types-is-native-error" | ||
version: 1.0.0 | ||
description: Handle DEP0197 via transforming `types.isNativeError` to `Error.isError` | ||
author: Bruno Rodrigues | ||
license: MIT | ||
workflow: workflow.yaml | ||
category: migration | ||
|
||
targets: | ||
languages: | ||
- javascript | ||
- typescript | ||
|
||
keywords: | ||
- transformation | ||
- migration | ||
|
||
registry: | ||
access: public | ||
visibility: public |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "@nodejs/types-is-native-error", | ||
"version": "1.0.0", | ||
"description": "Handle DEP0197 via transforming `types.isNativeError` to `Error.isError`", | ||
"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/rmdirs", | ||
"bugs": "https://github.com/nodejs/userland-migrations/issues" | ||
}, | ||
"author": "Bruno Rodrigues", | ||
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/types-is-native-error/README.md", | ||
"devDependencies": { | ||
"@codemod.com/jssg-types": "^1.0.3" | ||
}, | ||
"dependencies": { | ||
"@nodejs/codemod-utils": "*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,146 @@ | ||||||
import type { Edit, Range, SgNode, 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 { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path"; | ||||||
import { removeLines } from "@nodejs/codemod-utils/ast-grep/remove-lines"; | ||||||
import { removeBinding } from "@nodejs/codemod-utils/ast-grep/remove-binding"; | ||||||
|
||||||
type Binding = { | ||||||
path: string; | ||||||
lastPropertyAccess?: string; | ||||||
propertyAccess?: string; | ||||||
depth: number; | ||||||
node: SgNode; | ||||||
}; | ||||||
|
||||||
/** | ||||||
* Extracts property access information from a dot-notation path string. | ||||||
* | ||||||
* @param path - A dot-notation string representing a property path (e.g., "object.property.subProperty") | ||||||
* @returns An object containing: | ||||||
* - `path`: The original path string | ||||||
* - `lastPropertyAccess`: The last segment of the path (e.g., "subProperty" from "object.property.subProperty") | ||||||
* - `propertyAccess`: The path without the last segment (e.g., "object.property" from "object.property.subProperty") | ||||||
* - `depth`: The number of segments in the path | ||||||
* | ||||||
* @example | ||||||
* ```typescript | ||||||
* removeLastPropertyAccess("foo.bar.baz"); | ||||||
* // Returns: { path: "foo.bar.baz", propertyAccess: "foo.bar", lastPropertyAccess: "baz", depth: 3 } | ||||||
* | ||||||
* removeLastPropertyAccess("foo"); | ||||||
* // Returns: { path: "foo", propertyAccess: "", lastPropertyAccess: "foo", depth: 1 } | ||||||
* ``` | ||||||
*/ | ||||||
function removeLastPropertyAccess( | ||||||
path: string, | ||||||
): Pick<Binding, "path" | "lastPropertyAccess" | "propertyAccess" | "depth"> { | ||||||
const pathArr = path.split("."); | ||||||
|
||||||
if (!pathArr) { | ||||||
return { | ||||||
path, | ||||||
depth: 1, | ||||||
}; | ||||||
} | ||||||
|
||||||
const lastPropertyAccess = pathArr.at(-1); | ||||||
const propertyAccess = pathArr.slice(0, pathArr.length - 1).join("."); | ||||||
brunocroh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
if (!propertyAccess) { | ||||||
return { | ||||||
path, | ||||||
propertyAccess, | ||||||
lastPropertyAccess, | ||||||
depth: pathArr.length, | ||||||
}; | ||||||
} | ||||||
|
||||||
return { | ||||||
path, | ||||||
propertyAccess, | ||||||
lastPropertyAccess, | ||||||
depth: pathArr.length, | ||||||
}; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are the same O.o |
||||||
} | ||||||
|
||||||
/** | ||||||
* Transforms `util.types.isNativeError` usage to `Error.isError`. | ||||||
* | ||||||
* This transformation handles various import/require patterns and usage scenarios: | ||||||
* | ||||||
* 1. Identifies all require/import statements from 'node:util' or 'util' module that | ||||||
* include access to `types.isNativeError` | ||||||
* | ||||||
* 2. Replaces all matching code references: | ||||||
* - `util.types.isNativeError(...)` → `Error.isError(...)` | ||||||
* - `types.isNativeError(...)` → `Error.isError(...)` | ||||||
* - `isNativeError(...)` → `Error.isError(...)` | ||||||
* | ||||||
* 3. Removes unused bindings when all references to the imported/required | ||||||
* isNativeError have been replaced | ||||||
* | ||||||
*/ | ||||||
export default function transform(root: SgRoot<JS>): string | null { | ||||||
const rootNode = root.root(); | ||||||
const bindings: Binding[] = []; | ||||||
const edits: Edit[] = []; | ||||||
const linesToRemove: Range[] = []; | ||||||
|
||||||
// @ts-ignore - ast-grep types are not fully compatible with JSSG types | ||||||
const nodeRequires = getNodeRequireCalls(root, "util"); | ||||||
// @ts-ignore - ast-grep types are not fully compatible with JSSG types | ||||||
brunocroh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
const nodeImports = getNodeImportStatements(root, "util"); | ||||||
const path = "$.types.isNativeError"; | ||||||
|
||||||
for (const stmt of [...nodeRequires, ...nodeImports]) { | ||||||
const bindToReplace = resolveBindingPath(stmt, path); | ||||||
|
||||||
if (!bindToReplace) { | ||||||
continue; | ||||||
} | ||||||
|
||||||
bindings.push({ | ||||||
...removeLastPropertyAccess(bindToReplace), | ||||||
node: stmt, | ||||||
}); | ||||||
} | ||||||
|
||||||
for (const binding of bindings) { | ||||||
const nodes = rootNode.findAll({ | ||||||
rule: { | ||||||
pattern: `${binding.propertyAccess || binding.path}${binding.depth > 1 ? ".$$$FN" : ""}`, | ||||||
}, | ||||||
}); | ||||||
|
||||||
const nodesToEdit = rootNode.findAll({ | ||||||
rule: { | ||||||
pattern: binding.path, | ||||||
}, | ||||||
}); | ||||||
|
||||||
for (const node of nodesToEdit) { | ||||||
edits.push(node.replace("Error.isError")); | ||||||
} | ||||||
|
||||||
if (nodes.length === nodesToEdit.length) { | ||||||
const result = removeBinding( | ||||||
// @ts-ignore - ast-grep types are not fully compatible with JSSG types | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't necessary any more, so removed |
||||||
binding.node, | ||||||
binding.path.split(".").at(0), | ||||||
); | ||||||
|
||||||
if (result?.edit) { | ||||||
edits.push(result.edit); | ||||||
} | ||||||
|
||||||
if (result?.lineToRemove) { | ||||||
linesToRemove.push(result.lineToRemove); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
const sourceCode = rootNode.commitEdits(edits); | ||||||
return removeLines(sourceCode, linesToRemove); | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const { | ||
types: { isMap }, | ||
} = require("util"); | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const { types: test } = require("util"); | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (test.isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const err = new Error(); | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const { types } = require("util"); | ||
|
||
if (Error.isError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (types.isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
const { | ||
types: { isNativeError, isMap }, | ||
} = require("util"); | ||
|
||
if (isNativeError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { types } from "node:util"; | ||
|
||
if (types.isNativeError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { types } from "node:util"; | ||
|
||
if (types.isNativeError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const { types: test } = require("util"); | ||
|
||
if (test.isNativeError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (test.isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const util = require("node:util"); | ||
const { types } = require("util"); | ||
const { | ||
types: { isNativeError }, | ||
} = require("util"); | ||
const err = new Error(); | ||
|
||
if (util.types.isNativeError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (types.isNativeError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (isNativeError(err)) { | ||
// handle the error | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const { types } = require("util"); | ||
|
||
if (types.isNativeError(err)) { | ||
// handle the error | ||
} | ||
|
||
if (types.isMap([])) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"allowImportingTsExtensions": true, | ||
"allowJs": true, | ||
"alwaysStrict": true, | ||
"baseUrl": "./", | ||
"declaration": true, | ||
"declarationMap": true, | ||
"emitDeclarationOnly": true, | ||
"lib": ["ESNext", "DOM"], | ||
"module": "NodeNext", | ||
"moduleResolution": "NodeNext", | ||
"noImplicitThis": true, | ||
"removeComments": true, | ||
"strict": true, | ||
"stripInternal": true, | ||
"target": "esnext" | ||
}, | ||
"include": ["./"], | ||
"exclude": [ | ||
"tests/**" | ||
] | ||
} |
Uh oh!
There was an error while loading. Please reload this page.