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
Copy link
Member

Choose a reason for hiding this comment

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

This has some changes that I think should not be here.

Copy link
Member

Choose a reason for hiding this comment

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

if you look at commit history npm was doing some crazy thing after a merge commit. So idk how to fix that

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).

## 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);
* ```
*/
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",
},
]
},
});

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