Skip to content

Commit 78f8df8

Browse files
committed
Merge remote-tracking branch 'origin/main' into chenjiahan/perf-minify-fmt-worker
# Conflicts: # packages/rstack/rslib.config.ts
2 parents 0b3890f + e256fe8 commit 78f8df8

25 files changed

Lines changed: 1365 additions & 159 deletions

packages/rstack/THIRD_PARTY_NOTICES.md

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2929
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3030
SOFTWARE.
3131

32-
## @prettier/plugin-yuku
32+
## Prettier yuku parser adapter
3333

34-
This package includes bundled code from
35-
[@prettier/plugin-yuku](https://github.com/prettier/prettier/tree/main/packages/plugin-yuku).
34+
The local Yuku parser adapter includes portions derived from
35+
[@prettier/plugin-yuku](https://github.com/prettier/prettier/tree/main/packages/plugin-yuku)
36+
and Prettier's JavaScript parser postprocessing. The adapter reuses the public
37+
ESTree printer, formatter options, and parser utilities from the installed
38+
`prettier` package.
3639

3740
License: MIT
3841

@@ -56,38 +59,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
5659
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
5760
SOFTWARE.
5861

59-
The bundled plugin also contains MIT-licensed code from:
60-
61-
- emoji-regex 10.6.0, copyright Mathias Bynens
62-
- escape-string-regexp 5.0.0, copyright Sindre Sorhus
63-
- get-east-asian-width 1.6.0, copyright Sindre Sorhus
64-
- index-to-position 1.2.0, copyright Sindre Sorhus
65-
- is-es5-identifier-name 1.0.1, copyright fisker Cheung
66-
- jest-docblock 30.4.0, copyright Meta Platforms, Inc. and Jest contributors
67-
- narrow-emojis 0.0.3, copyright fisker Cheung
68-
- Prettier 3.10.0-dev, copyright James Long and contributors
69-
- to-fast-properties 4.0.0, copyright Petka Antonov, Benjamin Gruenbaum,
70-
John-David Dalton, and Sindre Sorhus
71-
- trim-newlines 5.0.0, copyright Sindre Sorhus
72-
73-
Permission is hereby granted, free of charge, to any person obtaining a copy of
74-
this software and associated documentation files (the "Software"), to deal in
75-
the Software without restriction, including without limitation the rights to
76-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
77-
of the Software, and to permit persons to whom the Software is furnished to do
78-
so, subject to the following conditions:
79-
80-
The above copyright notices and this permission notice shall be included in all
81-
copies or substantial portions of the Software.
82-
83-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
84-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
85-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
86-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
87-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
89-
SOFTWARE.
90-
9162
## fast-ignore
9263

9364
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
@@ -62,7 +62,6 @@
6262
"yuku-parser": "catalog:"
6363
},
6464
"devDependencies": {
65-
"@prettier/plugin-yuku": "catalog:",
6665
"@rspress/core": "catalog:",
6766
"@rstackjs/load-config": "catalog:",
6867
"@rstackjs/test-utils": "catalog:",

packages/rstack/rslib.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineConfig } from '@rslib/core';
22
import pkgJson from './package.json' with { type: 'json' };
33

4-
const fullyMinifiedChunks = /(?:fmtWorker|lintStaged)\.js$/;
4+
const fullyMinifiedChunks = /(?:fmtWorker|staged)\.js$/;
55

66
export default defineConfig({
77
lib: [{ syntax: 'es2023', dts: true }],
@@ -32,7 +32,7 @@ export default defineConfig({
3232
css: false,
3333
jsOptions: [
3434
{
35-
// Fully minify large bundles to reduce package size.
35+
// Fully minify the formatter worker and staged chunk to reduce package size.
3636
include: fullyMinifiedChunks,
3737
},
3838
{

packages/rstack/src/cli/commands.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { join } from 'node:path';
22
import { color } from 'rslog';
33
import { getConfigState } from '../config.ts';
4-
import { runSetupCLI } from '../setup/index.ts';
5-
import { runStagedCLI } from '../staged.ts';
64
import { insertConfigArg, parseCliArgs } from './args.ts';
75

86
declare global {
@@ -154,11 +152,19 @@ export async function setupCommands(): Promise<void> {
154152
}
155153

156154
if (command === 'staged') {
155+
const { runStagedCLI } = await import(
156+
/* rspackChunkName: 'staged' */
157+
'../staged.ts'
158+
);
157159
await runStagedCLI(args.slice(1));
158160
return;
159161
}
160162

161163
if (command === 'setup') {
164+
const { runSetupCLI } = await import(
165+
/* rspackChunkName: 'setup' */
166+
'../setup/index.ts'
167+
);
162168
runSetupCLI(args.slice(1));
163169
return;
164170
}

packages/rstack/src/fmt/cli.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ import { loadRstackConfig } from '../config.ts';
66
import { resolveFmtConfig } from './config.ts';
77
import { discoverFmtFiles } from './discovery.ts';
88
import { runFmtFiles } from './runner.ts';
9-
import type { FmtMode, FmtRunResult } from './types.ts';
9+
import { runFmtStdin } from './stdin.ts';
10+
import type { FmtMode, FmtRunResult, ResolvedFmtConfig } from './types.ts';
1011

1112
interface ParsedFmtCLIArgs {
1213
mode: FmtMode;
1314
patterns: string[];
1415
maxWorkers?: number;
1516
help: boolean;
17+
/** Path the stdin content is formatted as; it need not exist on disk. */
18+
stdinFilepath?: string;
1619
}
1720

1821
const fmtHelpMessage: string = `Rstack v${RSTACK_VERSION}
@@ -27,6 +30,7 @@ ${color.cyan('Options')}:
2730
--check Check whether files are formatted
2831
--list-different Print paths of unformatted files
2932
--parallel-workers <count> Number of parallel workers
33+
--stdin-filepath <path> Format stdin as if it were saved at <path>
3034
-h, --help Display this help message`;
3135

3236
const parseMaxWorkers = (
@@ -56,6 +60,8 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
5660
listDifferent: { type: 'boolean' },
5761
'parallel-workers': { type: 'string' },
5862
parallelWorkers: { type: 'string' },
63+
'stdin-filepath': { type: 'string' },
64+
stdinFilepath: { type: 'string' },
5965
help: { type: 'boolean', short: 'h' },
6066
},
6167
allowPositionals: true,
@@ -70,12 +76,26 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
7076

7177
const mode = values.check ? 'check' : listDifferent ? 'list-different' : 'write';
7278
const maxWorkers = parseMaxWorkers(values['parallel-workers'], values.parallelWorkers);
79+
const stdinFilepath = values['stdin-filepath'] ?? values.stdinFilepath;
80+
81+
if (stdinFilepath !== undefined) {
82+
if (modes.length > 0) {
83+
throw new Error(
84+
'The --stdin-filepath option cannot be used with --write, --check, or --list-different.',
85+
);
86+
}
87+
88+
if (positionals.length > 0) {
89+
throw new Error('The --stdin-filepath option cannot be used with file arguments.');
90+
}
91+
}
7392

7493
return {
7594
mode,
7695
patterns: positionals,
7796
maxWorkers,
7897
help: values.help ?? false,
98+
stdinFilepath,
7999
};
80100
};
81101

@@ -174,23 +194,35 @@ const logFmtResult = (
174194
}
175195
};
176196

177-
const runFmtCLI = async (args: string[]): Promise<void> => {
178-
const { help, maxWorkers, mode, patterns } = parseFmtCLIArgs(args);
179-
if (help) {
180-
logger.log(fmtHelpMessage);
181-
return;
182-
}
197+
const loadFmtConfig = async (cwd: string): Promise<ResolvedFmtConfig> => {
198+
const { configs, filePath } = await loadRstackConfig();
183199

200+
return resolveFmtConfig({
201+
definition: configs.fmt,
202+
configFilePath: filePath,
203+
cwd,
204+
});
205+
};
206+
207+
const runFmtCLI = async (args: string[]): Promise<void> => {
184208
const cwd = process.cwd();
185209
const startTime = performance.now();
186210

211+
// Argument errors are reported like every other failure so that a single
212+
// exit code identifies "rs fmt refused to run".
187213
try {
188-
const { configs, filePath } = await loadRstackConfig();
189-
const config = await resolveFmtConfig({
190-
definition: configs.fmt,
191-
configFilePath: filePath,
192-
cwd,
193-
});
214+
const { help, maxWorkers, mode, patterns, stdinFilepath } = parseFmtCLIArgs(args);
215+
if (help) {
216+
logger.log(fmtHelpMessage);
217+
return;
218+
}
219+
220+
if (stdinFilepath !== undefined) {
221+
await runFmtStdin({ filepath: stdinFilepath, cwd, loadConfig: () => loadFmtConfig(cwd) });
222+
return;
223+
}
224+
225+
const config = await loadFmtConfig(cwd);
194226
const files = await discoverFmtFiles({ cwd, patterns, config });
195227

196228
if (files.length === 0) {

packages/rstack/src/fmt/discoverPaths.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ class GitIgnoreMatcher {
164164
}
165165

166166
#matches(relativePath: string, isDirectory: boolean): boolean {
167+
// Most repositories only use a root `.gitignore`. Avoid checking every path
168+
// segment when no nested matcher can override its result.
169+
const rootMatcher = this.#matchers.size === 1 ? this.#matchers.get(this.#rootPath) : undefined;
170+
if (rootMatcher) {
171+
// `ignore` expects POSIX separators and uses a trailing slash to distinguish directories.
172+
const pathFromMatcher = toPosixPath(relativePath);
173+
return rootMatcher.test(isDirectory ? `${pathFromMatcher}/` : pathFromMatcher).ignored;
174+
}
175+
167176
const segments = relativePath.split(path.sep);
168177
let directoryPath = this.#rootPath;
169178
let pathFromMatcher = segments.join('/');
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { resolveFmtOptions } from './config.ts';
22
import { discoverFmtPaths } from './discoverPaths.ts';
33
import { createFmtIgnoreMatcher } from './ignore.ts';
4-
import { createFmtPluginResolver, type FmtPluginResolver } from './plugins.ts';
54
import type { DiscoverFmtFilesOptions, FmtFileRequest, ResolvedFmtConfig } from './types.ts';
65

7-
const createFileRequest = (
8-
filePath: string,
9-
config: ResolvedFmtConfig,
10-
resolvePlugins: FmtPluginResolver,
11-
): FmtFileRequest => ({
6+
const createFileRequest = (filePath: string, config: ResolvedFmtConfig): FmtFileRequest => ({
127
path: filePath,
13-
options: resolvePlugins(resolveFmtOptions(filePath, config)),
8+
options: resolveFmtOptions(filePath, config),
149
});
1510

1611
/** Discovers worker-ready files without reading Prettier config files or `.prettierignore`. */
@@ -26,9 +21,18 @@ const discoverFmtFiles = async ({
2621

2722
const isFmtIgnored = createFmtIgnoreMatcher(config);
2823
const filePaths = candidates.filter((filePath) => !isFmtIgnored(filePath));
24+
const files = filePaths.map((filePath) => createFileRequest(filePath, config));
25+
if (!files.some((file) => file.options.plugins?.length)) {
26+
return files;
27+
}
28+
29+
const { createFmtPluginResolver } = await import(
30+
/* rspackChunkName: 'fmtPlugins' */
31+
'./plugins.ts'
32+
);
2933
const resolvePlugins = createFmtPluginResolver(config.rootPath);
3034

31-
return filePaths.map((filePath) => createFileRequest(filePath, config, resolvePlugins));
35+
return files.map((file) => ({ ...file, options: resolvePlugins(file.options) }));
3236
};
3337

34-
export { discoverFmtFiles };
38+
export { createFileRequest, discoverFmtFiles };

packages/rstack/src/fmt/format.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Derived from @prettier/cli, see THIRD_PARTY_NOTICES.md
2+
3+
import {
4+
format,
5+
getFileInfo,
6+
type FileInfoOptions,
7+
type Options as PrettierOptions,
8+
} from 'prettier';
9+
import { getPrettierPlugins } from './prettierPlugins.ts';
10+
import type { FmtFileRequest } from './types.ts';
11+
12+
type PrettierPlugins = NonNullable<PrettierOptions['plugins']>;
13+
14+
type FormatFmtSourceResult =
15+
{ status: 'unsupported' } | { status: 'formatted'; source: string; formatted: string };
16+
17+
const fileInfoOptions = {
18+
ignorePath: [],
19+
resolveConfig: false,
20+
withNodeModules: true,
21+
} satisfies FileInfoOptions;
22+
23+
/** Uses the configured parser or infers one without loading Prettier config. */
24+
const resolveFmtParser = async (
25+
filePath: string,
26+
options: PrettierOptions,
27+
plugins: PrettierPlugins,
28+
): Promise<PrettierOptions['parser'] | null> =>
29+
options.parser ??
30+
(
31+
await getFileInfo(filePath, {
32+
...fileInfoOptions,
33+
plugins,
34+
})
35+
).inferredParser;
36+
37+
/** Formats file contents, requesting the source only once a parser is known. */
38+
const formatFmtSource = async (
39+
{ path, options }: FmtFileRequest,
40+
readSource: () => string,
41+
): Promise<FormatFmtSourceResult> => {
42+
const plugins = await getPrettierPlugins(options, path);
43+
const parser = await resolveFmtParser(path, options, plugins);
44+
if (!parser) {
45+
return { status: 'unsupported' };
46+
}
47+
48+
const source = readSource();
49+
const formatted = await format(source, {
50+
...options,
51+
filepath: path,
52+
parser,
53+
plugins,
54+
});
55+
56+
return { status: 'formatted', source, formatted };
57+
};
58+
59+
export { formatFmtSource };

packages/rstack/src/fmt/prettierPlugins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import * as yukuPlugin from '@prettier/plugin-yuku';
21
import type { Options as PrettierOptions, Plugin } from 'prettier';
32
import type { ResolvedFmtOptions } from './types.ts';
3+
import { yukuPlugin } from './yukuPlugin.ts';
44

55
type PrettierPlugins = NonNullable<PrettierOptions['plugins']>;
66

0 commit comments

Comments
 (0)