Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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.

20 changes: 20 additions & 0 deletions recipes/process-assert-to-assert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# `process.assert` DEP0100

This recipe transforms the usage of `process.assert` to use `assert` module.

See [DEP0100](https://github.com/nodejs/userland-migrations/issues/197).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
See [DEP0100](https://github.com/nodejs/userland-migrations/issues/197).
See [DEP0100](https://nodejs.org/api/deprecations.html#DEP100).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ps: verify the link I am on mobile for this review


## Example

**Before:**

```js
process.assert(condition, "Assertion failed");
```

**After:**

```js
import assert from "node:assert";
assert(condition, "Assertion failed");
```
21 changes: 21 additions & 0 deletions recipes/process-assert-to-assert/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
schema_version: "1.0"
name: "@nodejs/process-assert-to-assert"
version: 1.0.0
description: Handle DEP0100 via transforming `process.assert` to `assert`.
author: matheusmorett2
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration

registry:
access: public
visibility: public
25 changes: 25 additions & 0 deletions recipes/process-assert-to-assert/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@nodejs/process-assert-to-assert",
"version": "1.0.0",
"description": "Handle DEP0100 via transforming `process.assert` to `assert`.",
"type": "module",
"scripts": {
"test": "npx codemod jssg test -l typescript --ignore-whitespace ./src/workflow.ts ./",
"test:u": "npx codemod jssg test -l typescript -u ./src/workflow.ts ./"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nodejs/userland-migrations.git",
"directory": "recipes/process-assert-to-assert",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "matheusmorett2",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/process-assert-to-assert/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.0.3"
},
"dependencies": {
"@nodejs/codemod-utils": "*"
}
}
121 changes: 121 additions & 0 deletions recipes/process-assert-to-assert/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { Edit, Range, Rule, SgNode, SgRoot } 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";

/**
* Transforms deprecated `process.assert` usage to the standard `assert` module.
*
* Transformations:
* 1. Replaces all `process.assert` references with `assert`
* 2. Adds the necessary import/require statement if not already present:
* - For ESM or files without require calls: adds `import assert from "node:assert"`
* - For CommonJS (.cjs files or files using require): adds `const assert = require("node:assert")`
*
* Examples:
*
* Before:
* ```js
* process.assert(value);
* process.assert.strictEqual(a, b);
* ```
*
* After:
* ```js
* import assert from "node:assert";
* assert(value);
* assert.strictEqual(a, b);
* ```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove process.assert.strictEqual reference, I think it was added by mistake. This scenario does not exist. Add bold formatting to the before/after text.

Suggested change
* Before:
* ```js
* process.assert(value);
* process.assert.strictEqual(a, b);
* ```
*
* After:
* ```js
* import assert from "node:assert";
* assert(value);
* assert.strictEqual(a, b);
* ```
* **Before**:
* ```js
* process.assert(value);
* ```
*
* **After**:
* ```js
* import assert from "node:assert";
* assert(value);
* ```

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually my code wasn't working for assert.strictEqual but is should

Screenshot 2025-09-06 at 5 15 23 PM

https://nodejs.org/api/assert.html

so I kept the assert.strictEqual

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added the bold on before and after words

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that the TypeScript docs say that the code in the before block, process.assert.strictEqual(a, b), will be updated to use assert.strictEqual.

However, process.assert.strictEqual does not exist, at least I couldn’t find any reference to it.

*/
export default function transform(root: SgRoot): string | null {
const rootNode = root.root();
const edits: Edit[] = [];
const linesToRemove: Range[] = [];
const replaceRules: Array<
{
importNode?: SgNode
binding?: string
rule: Rule
}
> = [{
rule: {
kind: 'member_expression',
pattern: "process.assert",
}
}];

const requireProcess = getNodeRequireCalls(root, "process");
const importProcess = getNodeImportStatements(root, "process");

const allProcessImports = [...requireProcess, ...importProcess]

for (const processImport of allProcessImports) {
const binding = resolveBindingPath(processImport, "$.assert");
replaceRules.push(
{
importNode: processImport,
binding,
rule: {
kind: "identifier",
regex: binding,
inside: {
kind: 'call_expression',
}
}
}
)
}

for (const replaceRule of replaceRules) {
const nodes = rootNode.findAll({
rule: replaceRule.rule
})

for (const node of nodes) {
if (replaceRule.importNode) {
const removeBind = removeBinding(replaceRule.importNode, replaceRule.binding)

if (removeBind.edit) {
edits.push(removeBind.edit);
}

if (removeBind.lineToRemove) {
linesToRemove.push(removeBind.lineToRemove)
}
}

edits.push(node.replace("assert"))
}
}

let sourceCode = rootNode.commitEdits(edits);
sourceCode = removeLines(sourceCode, linesToRemove);

const alreadyRequiringAssert = getNodeRequireCalls(root, "assert");
const alreadyImportingAssert = getNodeImportStatements(root, "assert");

if (!alreadyRequiringAssert.length && !alreadyImportingAssert.length) {
const usingRequire = rootNode.find({
rule: {
kind: 'call_expression',
has: {
kind: 'identifier',
field: 'function',
regex: 'require'
}
}
})

const isCommonJs = root.filename().includes('.cjs')

if (Boolean(usingRequire) || isCommonJs) {
return `const assert = require("node:assert");\n${sourceCode}`
}

return `import assert from "node:assert";\n${sourceCode}`
}

return sourceCode
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import assert from "node:assert";
function validateInput(input) {
assert(typeof input === "string", "Input must be string");
return input.trim();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import assert from "node:assert";
assert(config.port, "Port must be configured");
assert(config.port, "Port must be configured");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import assert from "node:assert";
assert(condition, "Assertion failed");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const assert = require("node:assert");
assert(config.port, "Port must be configured");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import assert from "node:assert";
import { env } from "process";
assert(condition, "Assertion valid");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const assert = require("node:assert");
const { env } = require("process");
assert(condition, "Assertion valid");
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const assert = require("node:assert");
const util = require("node:util");
assert(config.port, "Port must be configured");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function validateInput(input) {
process.assert(typeof input === "string", "Input must be string");
return input.trim();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import assert from "node:assert";

process.assert(config.port, "Port must be configured");
assert(config.port, "Port must be configured");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.assert(condition, "Assertion failed");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.assert(config.port, "Port must be configured");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { assert as nodeAssert, env } from "process";
nodeAssert(condition, "Assertion valid");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const { assert: nodeAssert, env } = require("process");
nodeAssert(condition, "Assertion valid");
2 changes: 2 additions & 0 deletions recipes/process-assert-to-assert/tests/input/using-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const util = require("node:util");
process.assert(config.port, "Port must be configured");
25 changes: 25 additions & 0 deletions recipes/process-assert-to-assert/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 DEP0100 via transforming `process.assert` to `assert`.
js-ast-grep:
js_file: src/workflow.ts
base_path: .
include:
- "**/*.cjs"
- "**/*.js"
- "**/*.jsx"
- "**/*.mjs"
- "**/*.cts"
- "**/*.mts"
- "**/*.ts"
- "**/*.tsx"
exclude:
- "**/node_modules/**"
language: typescript
23 changes: 23 additions & 0 deletions utils/src/ast-grep/remove-binding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ describe("remove-binding", () => {
});
});

it("should not remove non-related named requires", () => {
const code = dedent`
const { assert: nodeAssert, env } = require("process");
`;

const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
const node = rootNode.root();

const requireStatement = node.find({
rule: {
kind: "lexical_declaration",
},
});

const change = removeBinding(requireStatement!, "nodeAssert");

const sourceCode = node.commitEdits([change?.edit!]);
assert.strictEqual(sourceCode, `const { env } = require("process");`);

assert.notEqual(change, null);
assert.strictEqual(change?.lineToRemove, undefined);
})

it("should return undefined when the binding does not match the imported name", () => {
const code = dedent`
const util = require('node:util');
Expand Down
22 changes: 20 additions & 2 deletions utils/src/ast-grep/remove-binding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,14 @@ function handleNamedRequireBindings(

const declarations = node.findAll({
rule: {
kind: "shorthand_property_identifier_pattern",
any: [
{
kind: "pair_pattern",
},
{
kind: "shorthand_property_identifier_pattern",
},
Comment on lines +188 to +193
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
kind: "pair_pattern",
},
{
kind: "shorthand_property_identifier_pattern",
},
{
kind: "pair_pattern" },
{ kind: "shorthand_property_identifier_pattern" },

I found that simplest to read idk what you think about that

]
},
});

Expand All @@ -195,7 +202,18 @@ function handleNamedRequireBindings(
}

if (declarations.length > 1) {
const restDeclarations = declarations.map((d) => d.text()).filter((d) => d !== binding);
const restDeclarations = declarations.map((d) => {
if (d.kind() === 'pair_pattern') {
const alias = d.find({
rule: {
kind: 'identifier',
}
})

return alias.text()
}
return d.text()
}).filter((d) => d !== binding);

return {
edit: objectPattern.replace(`{ ${restDeclarations.join(", ")} }`),
Expand Down