Skip to content
Merged
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
24 changes: 15 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions recipes/fs-access-mode-constants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` DEP0176

Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`.

See [DEP0176](https://nodejs.org/api/deprecations.html#dep0176-fsf_ok-fsr_ok-fsw_ok-fsx_ok)

## Example

**Before:**
```js
const fs = require('node:fs');

fs.access('/path/to/file', fs.F_OK, callback);
fs.access('/path/to/file', fs.R_OK | fs.W_OK, callback);
```

**After:**
```js
const fs = require('node:fs');

fs.access('/path/to/file', fs.constants.F_OK, callback);
fs.access('/path/to/file', fs.constants.R_OK | fs.constants.W_OK, callback);
```
21 changes: 21 additions & 0 deletions recipes/fs-access-mode-constants/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
schema_version: "1.0"
name: "@nodejs/fs-access-mode-constants"
version: "1.0.0"
description: Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`.
author: nekojanai (Jana)
license: MIT
workflow: workflow.yaml
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration

registry:
access: public
visibility: public
24 changes: 24 additions & 0 deletions recipes/fs-access-mode-constants/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@nodejs/fs-access-mode-constants",
"version": "1.0.0",
"description": "Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`.",
"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/fs-access-mode-constants",
"bugs": "https://github.com/nodejs/userland-migrations/issues"
},
"author": "nekojanai (Jana)",
"license": "MIT",
"homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/fs-access-mode-constants/README.md",
"dependencies": {
"@nodejs/codemod-utils": "*"
},
"devDependencies": {
"@codemod.com/jssg-types": "^1.0.3"
}
}
76 changes: 76 additions & 0 deletions recipes/fs-access-mode-constants/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Edit, SgRoot } from "@codemod.com/jssg-types/main";
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
import type Js from "@codemod.com/jssg-types/langs/javascript";
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";

export default function tranform(root: SgRoot<Js>): string | null {
const rootNode = root.root();
const edits: Edit[] = [];

// @ts-expect-error - ast-grep types are not fully compatible with JSSG types
const requireStatements = getNodeRequireCalls(root, "fs");

for (const statement of requireStatements) {
const objectPattern = statement.find({
rule:
{ kind: 'object_pattern' },
});

if (objectPattern) {
let objPatArr = objectPattern.findAll({
rule:
{ kind: 'shorthand_property_identifier_pattern' },
}).map((v) => v.text());
objPatArr = objPatArr.filter((v) => !['F_OK', 'R_OK', 'W_OK', 'X_OK'].includes(v));
objPatArr.push('constants');
edits.push(objectPattern.replace(`{ ${objPatArr.join(', ')} }`));
};
}

// @ts-expect-error - ast-grep types are not fully compatible with JSSG types
const importStatements = getNodeImportStatements(root, "fs");
let promisesImportName = '';

for (const statement of importStatements) {
const objectPattern = statement.find({
rule:
{ kind: 'named_imports' },
});

if (objectPattern) {
let objPatArr = objectPattern.findAll({
rule:
{ kind: 'import_specifier' },
}).map((v) => v.text());
Comment on lines +41 to +44
Copy link
Member

Choose a reason for hiding this comment

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

Nit: the formatting makes this a bit difficult to grok

Suggested change
let objPatArr = objectPattern.findAll({
rule:
{ kind: 'import_specifier' },
}).map((v) => v.text());
let objPatArr = objectPattern.findAll({
rule: { kind: 'import_specifier' },
}).map((v) => v.text());

objPatArr = objPatArr.filter((v) => !['F_OK', 'R_OK', 'W_OK', 'X_OK'].includes(v));
const promisesImport = objPatArr.find((v) => v.startsWith('promises'));
if (promisesImport) {
if (promisesImport.includes('as')) {
const m = promisesImport.matchAll((/promises as (\w+)/g));
m.forEach((v) => promisesImportName = v[1] ?? 'promises');
} else {
promisesImportName = promisesImport;
}
promisesImportName = `${promisesImportName}.`
} else {
objPatArr.push('constants');
}
edits.push(objectPattern.replace(`{ ${objPatArr.join(', ')} }`));
}
}

for (const _OK of ['F_OK', 'R_OK', 'W_OK', 'X_OK']) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this array is declared and used multiple times. It could instead be declared as a const in the root scope and then referenced.

for (const [prefix, replacement] of [['fs.', 'fs.constants.'], ['', `${promisesImportName ? promisesImportName : ''}constants.`]]) {
const patterns = rootNode.findAll({
rule: {
pattern: `${prefix}${_OK}`
}
});
for (const pattern of patterns) {
edits.push(pattern.replace(`${replacement}${_OK}`));
}
}
}

return rootNode.commitEdits(edits);
}
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-00.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const fs = require('node:fs');

fs.access('/path/to/file', fs.constants.F_OK, callback);
fs.access('/path/to/file', fs.constants.R_OK | fs.constants.W_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-01.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as fs from 'node:fs';

fs.access('/path/to/file', fs.constants.F_OK, callback);
fs.access('/path/to/file', fs.constants.X_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-02.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { access, constants } = require('node:fs');

access('/path/to/file', constants.F_OK, callback);
access('/path/to/file', constants.R_OK | constants.W_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-03.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { access, constants } from 'node:fs';

access('/path/to/file', constants.F_OK, callback);
access('/path/to/file', constants.R_OK | constants.W_OK | constants.X_OK, callback);
5 changes: 5 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-04.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { accessSync, constants } = require('node:fs');
const fs = require('node:fs');

accessSync('/path/to/file', constants.F_OK);
fs.access('/path/to/file', fs.constants.W_OK | constants.R_OK, callback);
3 changes: 3 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-05.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { promises as fsPromises } from 'node:fs';

await fsPromises.access('/path/to/file', fsPromises.constants.F_OK);
6 changes: 6 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-06.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fs = require('node:fs');

const mode = fs.constants.R_OK | fs.constants.W_OK;
if (condition) {
fs.accessSync('/path/to/file', fs.constants.F_OK);
}
6 changes: 6 additions & 0 deletions recipes/fs-access-mode-constants/tests/expected/file-07.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { constants } from 'node:fs';

const readable = constants.R_OK;
const writable = constants.W_OK;
const executable = constants.X_OK;
const exists = constants.F_OK;
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-00.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const fs = require('node:fs');

fs.access('/path/to/file', fs.F_OK, callback);
fs.access('/path/to/file', fs.R_OK | fs.W_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-01.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as fs from 'node:fs';

fs.access('/path/to/file', fs.F_OK, callback);
fs.access('/path/to/file', fs.X_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-02.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { access, F_OK, R_OK, W_OK } = require('node:fs');

access('/path/to/file', F_OK, callback);
access('/path/to/file', R_OK | W_OK, callback);
4 changes: 4 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-03.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { access, F_OK, R_OK, W_OK, X_OK } from 'node:fs';

access('/path/to/file', F_OK, callback);
access('/path/to/file', R_OK | W_OK | X_OK, callback);
5 changes: 5 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-04.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { accessSync, F_OK, R_OK } = require('node:fs');
const fs = require('node:fs');

accessSync('/path/to/file', F_OK);
fs.access('/path/to/file', fs.W_OK | R_OK, callback);
3 changes: 3 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-05.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { promises as fsPromises, F_OK, R_OK } from 'node:fs';

await fsPromises.access('/path/to/file', F_OK);
6 changes: 6 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-06.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const fs = require('node:fs');

const mode = fs.R_OK | fs.W_OK;
Copy link
Member

@JakobJingleheimer JakobJingleheimer Aug 12, 2025

Choose a reason for hiding this comment

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

Is this deliberately using a binary OR (|) instead of a logical OR (||)?

Copy link
Member

Choose a reason for hiding this comment

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

if (condition) {
Copy link
Member

Choose a reason for hiding this comment

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

Is condition supposed to be mode? (or vice versa)

Copy link
Member

Choose a reason for hiding this comment

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

@nekojanai could you answer about this though? (I know it doesn't especially matter, so fine to merge without the answer)

fs.accessSync('/path/to/file', fs.F_OK);
}
6 changes: 6 additions & 0 deletions recipes/fs-access-mode-constants/tests/input/file-07.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { F_OK, R_OK, W_OK, X_OK } from 'node:fs';

const readable = R_OK;
const writable = W_OK;
const executable = X_OK;
const exists = F_OK;
27 changes: 27 additions & 0 deletions recipes/fs-access-mode-constants/workflow.yaml
Original file line number Diff line number Diff line change
@@ -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: Handle DEP0176 via transforming imports of `fs.F_OK`, `fs.R_OK`, `fs.W_OK`, `fs.X_OK` from the root `fs` module to `fs.constants`.
js-ast-grep:
js_file: src/workflow.ts
base_path: .
include:
- "**/*.cjs"
- "**/*.js"
- "**/*.jsx"
- "**/*.mjs"
- "**/*.cts"
- "**/*.mts"
- "**/*.ts"
- "**/*.tsx"
exclude:
- "**/node_modules/**"
language: typescript
Loading