nodejs / nodejs/userland-migrations

`fs.write()` and `fs.writeFileSync()` coercion to string

Open
#412 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

good first issue human help wanted let's do it
Dominant language
TypeScript
Stars
85
Forks
55
Avg merge
3d 11h
Merged PRs (30d)
9

Description

Description

Since implicit coercion of objects in fs.write() and fs.writeFileSync() is deprecated (DEP0162) and has reached End-of-Life status in Node.js v19.0.0, we should provide a codemod to replace them.

  • The codemod should convert objects with toString() method to primitive strings before passing to fs write functions.
  • The codemod should handle fs.write(), fs.writeFile(), fs.appendFile(), fs.writeFileSync(), and fs.appendFileSync().
  • The codemod should detect objects being passed as the data parameter.
  • The codemod should add explicit .toString() calls or String() conversion.

Additional Information

Note that in Node.js v19.0.0, implicit coercion of objects with own toString property passed as the second parameter (data) in fs write operations now throws an error. This was deprecated because the implicit conversion could lead to unexpected behavior and made the code less explicit.

Instead of relying on implicit coercion, developers should explicitly convert objects to strings using .toString(), String(), or template literals.

Examples

Example 1: Object in fs.writeFileSync()

Before:

const fs = require("node:fs");

const data = { toString: () => "file content" };
fs.writeFileSync("file.txt", data);

After:

const fs = require("node:fs");

const data = { toString: () => "file content" };
fs.writeFileSync("file.txt", data.toString());
Example 2: Object in fs.writeFile()

Before:

const fs = require("node:fs");

const obj = { toString: () => "content" };
fs.writeFile("file.txt", obj, (err) => {
  if (err) throw err;
});

After:

const fs = require("node:fs");

const obj = { toString: () => "content" };
fs.writeFile("file.txt", obj.toString(), (err) => {
  if (err) throw err;
});
Example 3: Object in fs.appendFileSync()

Before:

const fs = require("node:fs");

const data = { toString: () => "appended data" };
fs.appendFileSync("file.txt", data);

After:

const fs = require("node:fs");

const data = { toString: () => "appended data" };
fs.appendFileSync("file.txt", data.toString());
Example 4: Object in fs.appendFile()

Before:

const fs = require("node:fs");

const content = { toString: () => "more content" };
fs.appendFile("file.txt", content, (err) => {
  if (err) throw err;
});

After:

const fs = require("node:fs");

const content = { toString: () => "more content" };
fs.appendFile("file.txt", content.toString(), (err) => {
  if (err) throw err;
});
Example 5: Object in fs.write()

Before:

const fs = require("node:fs");

const fd = fs.openSync("file.txt", "w");
const data = { toString: () => "buffer content" };
fs.write(fd, data, (err) => {
  if (err) throw err;
  fs.closeSync(fd);
});

After:

const fs = require("node:fs");

const fd = fs.openSync("file.txt", "w");
const data = { toString: () => "buffer content" };
fs.write(fd, data.toString(), (err) => {
  if (err) throw err;
  fs.closeSync(fd);
});
Example 6: Using String() conversion

Before:

const fs = require("node:fs");

const myObj = {
  value: 42,
  toString() {
    return `Value: ${this.value}`;
  }
};
fs.writeFileSync("output.txt", myObj);

After:

const fs = require("node:fs");

const myObj = {
  value: 42,
  toString() {
    return `Value: ${this.value}`;
  }
};
fs.writeFileSync("output.txt", String(myObj));
Example 7: ESM with promises API

Before:

import { writeFile } from "node:fs/promises";

const data = { toString: () => "async content" };
await writeFile("file.txt", data);

After:

import { writeFile } from "node:fs/promises";

const data = { toString: () => "async content" };
await writeFile("file.txt", data.toString());
Example 8: Valid types (no change needed)

Before:

const fs = require("node:fs");

// These are all valid and don't need changes
fs.writeFileSync("file1.txt", "string");
fs.writeFileSync("file2.txt", Buffer.from("buffer"));
fs.writeFileSync("file3.txt", new Uint8Array([1, 2, 3]));

After:

const fs = require("node:fs");

// These are all valid and don't need changes
fs.writeFileSync("file1.txt", "string");
fs.writeFileSync("file2.txt", Buffer.from("buffer"));
fs.writeFileSync("file3.txt", new Uint8Array([1, 2, 3]));

Refs

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The payload names no repository files, tests, or codemod entry point; start by locating the existing migration framework and its coverage for Node.js fs APIs. Use the listed fs methods, the node:fs/promises example, and the valid Buffer and Uint8Array cases to define coverage, then verify object coercion changes without altering valid types.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, typescript
Domain
tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.