Skip to content

Commit 606013e

Browse files
committed
Merge remote-tracking branch 'origin/main' into chenjiahan/perf-fmt-parser-worker
# Conflicts: # packages/rstack/src/fmt/worker.ts # packages/rstack/tests/fmt/worker.test.ts
2 parents a991157 + a3ef9f9 commit 606013e

7 files changed

Lines changed: 58 additions & 143 deletions

File tree

packages/rstack/THIRD_PARTY_NOTICES.md

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -88,35 +88,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
8888
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
8989
SOFTWARE.
9090

91-
## atomically
92-
93-
This package includes bundled code from
94-
[atomically](https://github.com/fabiospampinato/atomically).
95-
96-
License: MIT
97-
98-
The MIT License (MIT)
99-
100-
Copyright (c) 2020-present Fabio Spampinato
101-
102-
Permission is hereby granted, free of charge, to any person obtaining a
103-
copy of this software and associated documentation files (the "Software"),
104-
to deal in the Software without restriction, including without limitation
105-
the rights to use, copy, modify, merge, publish, distribute, sublicense,
106-
and/or sell copies of the Software, and to permit persons to whom the
107-
Software is furnished to do so, subject to the following conditions:
108-
109-
The above copyright notice and this permission notice shall be included in
110-
all copies or substantial portions of the Software.
111-
112-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
113-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
114-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
115-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
116-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
117-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
118-
DEALINGS IN THE SOFTWARE.
119-
12091
## fast-ignore
12192

12293
This package includes bundled code from [fast-ignore](https://github.com/fabiospampinato/fast-ignore).

packages/rstack/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
"@rstest/adapter-rslib": "catalog:",
7070
"@types/micromatch": "catalog:",
7171
"@types/node": "catalog:",
72-
"atomically": "catalog:",
7372
"fast-ignore": "catalog:",
7473
"ignore": "catalog:",
7574
"import-meta-resolve": "catalog:",

packages/rstack/src/fmt/worker.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
// Derived from @prettier/cli, see THIRD_PARTY_NOTICES.md
22

3-
import { readFile, writeFile } from 'atomically';
3+
import { readFileSync, writeFileSync } from 'node:fs';
44
import { format } from 'prettier';
55
import { resolveFmtParser } from './parser.ts';
66
import { getPrettierPlugins } from './prettierPlugins.ts';
77
import type { FmtFileRequest, FmtWorkerFileResult } from './types.ts';
88

99
/**
10-
* Formatting output can be regenerated, so avoid waiting for a durability sync
11-
* after every file, which is especially expensive during parallel formatting.
12-
* `atomically` still uses a temporary file and rename for atomic replacement.
10+
* Use synchronous direct I/O inside the dedicated worker to avoid libuv
11+
* scheduling overhead. This prioritizes throughput over crash-safe replacement.
1312
*/
14-
const atomicWriteOptions = {
15-
encoding: 'utf8',
16-
fsync: false,
17-
} as const;
18-
1913
const formatFile = async (
2014
{ path, options }: FmtFileRequest,
2115
shouldWrite: boolean,
@@ -26,7 +20,7 @@ const formatFile = async (
2620
return 'unsupported';
2721
}
2822

29-
const source = await readFile(path, 'utf8');
23+
const source = readFileSync(path, 'utf8');
3024
const formatted = await format(source, {
3125
...options,
3226
parser,
@@ -38,7 +32,7 @@ const formatFile = async (
3832
}
3933

4034
if (shouldWrite) {
41-
await writeFile(path, formatted, atomicWriteOptions);
35+
writeFileSync(path, formatted, 'utf8');
4236
}
4337

4438
return 'changed';

packages/rstack/tests/fmt/runnerWriteFailure.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ const mocks = rs.hoisted(() => ({
88
rs.mock('../../src/fmt/parallel.ts', () => ({
99
createFmtWorker: () =>
1010
Promise.resolve({
11-
formatFile: () => Promise.reject(new Error('atomic write failed')),
11+
formatFile: () => Promise.reject(new Error('file write failed')),
1212
terminate: () => {
1313
mocks.terminateCalls++;
1414
},
1515
}),
1616
}));
1717

18-
test('returns an error when the atomic write fails', async () => {
18+
test('returns an error when a file write fails', async () => {
1919
const filePath = '/virtual/example.ts';
2020

2121
const result = await runFmtFiles({
@@ -38,7 +38,7 @@ test('returns an error when the atomic write fails', async () => {
3838
{
3939
path: filePath,
4040
status: 'error',
41-
error: { message: 'atomic write failed' },
41+
error: { message: 'file write failed' },
4242
},
4343
],
4444
});
Lines changed: 50 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,61 @@
1-
import { beforeEach, expect, rs, test } from 'rstack/test';
1+
import { readFileSync } from 'node:fs';
2+
import path from 'node:path';
3+
import { expect, test } from 'rstack/test';
24
import { formatFile } from '../../src/fmt/worker.ts';
3-
4-
const mocks = rs.hoisted(() => ({
5-
readFileCalls: [] as string[],
6-
writeFileCalls: [] as [string, string, unknown][],
7-
}));
8-
9-
rs.mock('atomically', () => ({
10-
readFile: (path: string) => {
11-
mocks.readFileCalls.push(path);
12-
return Promise.resolve('const value=1');
13-
},
14-
writeFile: (path: string, data: string, options: unknown) => {
15-
mocks.writeFileCalls.push([path, data, options]);
16-
return Promise.resolve();
17-
},
18-
}));
19-
20-
beforeEach(() => {
21-
mocks.readFileCalls.length = 0;
22-
mocks.writeFileCalls.length = 0;
23-
});
24-
25-
test('disables fsync for atomic writes', async () => {
26-
const filePath = '/virtual/example.ts';
27-
28-
await expect(
29-
formatFile(
30-
{
31-
path: filePath,
32-
options: {
33-
filepath: filePath,
34-
parser: 'typescript',
5+
import { withTempProject, writeProjectFile } from './helpers.ts';
6+
7+
test('writes formatted files', async () => {
8+
await withTempProject(async (rootPath) => {
9+
const filePath = writeProjectFile(rootPath, 'example.ts', 'const value=1');
10+
11+
await expect(
12+
formatFile(
13+
{
14+
path: filePath,
15+
options: {
16+
filepath: filePath,
17+
parser: 'typescript',
18+
},
3519
},
36-
},
37-
true,
38-
),
39-
).resolves.toBe('changed');
20+
true,
21+
),
22+
).resolves.toBe('changed');
4023

41-
expect(mocks.writeFileCalls).toEqual([
42-
[filePath, 'const value = 1;\n', { encoding: 'utf8', fsync: false }],
43-
]);
24+
expect(readFileSync(filePath, 'utf8')).toBe('const value = 1;\n');
25+
});
4426
});
4527

4628
test('infers the parser before formatting', async () => {
47-
const filePath = '/virtual/example.ts';
48-
49-
await expect(
50-
formatFile(
51-
{
52-
path: filePath,
53-
options: { filepath: filePath },
54-
},
55-
false,
56-
),
57-
).resolves.toBe('changed');
29+
await withTempProject(async (rootPath) => {
30+
const source = 'const value=1';
31+
const filePath = writeProjectFile(rootPath, 'example.ts', source);
32+
33+
await expect(
34+
formatFile(
35+
{
36+
path: filePath,
37+
options: { filepath: filePath },
38+
},
39+
false,
40+
),
41+
).resolves.toBe('changed');
5842

59-
expect(mocks.readFileCalls).toEqual([filePath]);
60-
expect(mocks.writeFileCalls).toEqual([]);
43+
expect(readFileSync(filePath, 'utf8')).toBe(source);
44+
});
6145
});
6246

6347
test('skips unsupported files before reading them', async () => {
64-
const filePath = '/virtual/example.unknown';
65-
66-
await expect(
67-
formatFile(
68-
{
69-
path: filePath,
70-
options: { filepath: filePath },
71-
},
72-
true,
73-
),
74-
).resolves.toBe('unsupported');
75-
76-
expect(mocks.readFileCalls).toEqual([]);
77-
expect(mocks.writeFileCalls).toEqual([]);
48+
await withTempProject(async (rootPath) => {
49+
const filePath = path.join(rootPath, 'missing.unknown');
50+
51+
await expect(
52+
formatFile(
53+
{
54+
path: filePath,
55+
options: { filepath: filePath },
56+
},
57+
true,
58+
),
59+
).resolves.toBe('unsupported');
60+
});
7861
});

pnpm-lock.yaml

Lines changed: 0 additions & 31 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ catalog:
3535
'@types/react': '^19.2.18'
3636
'@types/react-dom': '^19.2.4'
3737
'@shikijs/transformers': '^4.3.1'
38-
atomically: '2.1.1'
3938
'cspell-ban-words': '^0.0.4'
4039
'fast-ignore': '2.0.0'
4140
'happy-dom': '^20.11.1'

0 commit comments

Comments
 (0)