fix(core): jsonl write([]) should leave an empty file, not a stray newline (#7533)

write() built its content with join('\n') and then appended a trailing
newline. For an empty array the join produces '', so the file ends up as
a single '\n': one byte, no records.

That makes the module's own accessors contradict each other — exists()
tests size > 0 and returns true, while read() skips blank lines and
returns []. Clearing a JSONL file therefore leaves something that reports
as non-empty but has nothing in it.

Terminate each record instead of joining with separators. The output for
a non-empty array is byte-identical; an empty array now writes nothing.

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
This commit is contained in:
chinesepowered 2026-07-23 04:50:51 -07:00 committed by GitHub
parent abc9666f77
commit baaedfbf7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 24 additions and 2 deletions

View file

@ -20,6 +20,7 @@ import {
_recoverObjectsFromLine,
_resetEnsuredDirsCacheForTest,
countLines,
exists,
parseLineTolerant,
read,
readLines,
@ -354,6 +355,24 @@ describe('writeLine / writeLineSync / write', () => {
// branch is never exercised. A regression that dropped that branch would
// make write() fail with ENOENT only when callers target a brand-new
// subdirectory.
it('write() with an empty array leaves a genuinely empty file', async () => {
const file = path.join(
tmpRoot,
`we-${Math.random().toString(36).slice(2)}.jsonl`,
);
await writeLine(file, { v: 1 });
expect(exists(file)).toBe(true);
// Clearing the file must not leave a stray newline behind: a 1-byte file
// makes exists() (size > 0) disagree with read() (no records).
write(file, []);
expect(fs.readFileSync(file, 'utf8')).toBe('');
expect(fs.statSync(file).size).toBe(0);
expect(await read(file)).toEqual([]);
expect(exists(file)).toBe(false);
});
it('write() creates parent dirs when missing', () => {
const nested = path.join(tmpRoot, 'a', 'b', 'c', 'file.jsonl');
write(nested, [{ x: 1 }]);

View file

@ -323,13 +323,16 @@ export function writeLineSync(filePath: string, data: unknown): void {
* Each object will be written as a separate line.
*/
export function write(filePath: string, data: unknown[]): void {
const lines = data.map((item) => JSON.stringify(item)).join('\n');
// Terminate each record rather than joining with separators: joining an
// empty array yields '' and the trailing newline then writes a 1-byte file
// that read() reports as empty but exists() reports as non-empty.
const lines = data.map((item) => `${JSON.stringify(item)}\n`).join('');
// Ensure directory exists before writing
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
atomicWriteFileSync(filePath, `${lines}\n`, { encoding: 'utf8' });
atomicWriteFileSync(filePath, lines, { encoding: 'utf8' });
}
/**