@@ -25,6 +25,8 @@ interface ParsedFmtCLIArgs {
2525 help : boolean ;
2626 /** Path the stdin content is formatted as; it need not exist on disk. */
2727 stdinFilepath ?: string ;
28+ /** Serve formatting over the Language Server Protocol instead of exiting. */
29+ lsp : boolean ;
2830}
2931
3032const renderFmtHelp = ( ) : string =>
@@ -46,6 +48,7 @@ const renderFmtHelp = (): string =>
4648 [ '--with-node-modules' , 'Process files inside node_modules' ] ,
4749 [ '--parallel-workers <count>' , 'Number of parallel workers' ] ,
4850 [ '--stdin-filepath <path>' , 'Format stdin as if it were saved at <path>' ] ,
51+ [ '--lsp' , 'Run a language server on stdio' ] ,
4952 [ '-c, --config <path>' , 'Specify Rstack config file path' ] ,
5053 [ '-h, --help' , 'Display this help message' ] ,
5154 ] ,
@@ -66,6 +69,19 @@ const parseMaxWorkers = (value: string | undefined): number | undefined => {
6669 return maxWorkers ;
6770} ;
6871
72+ /** Rejects the mode flags and file arguments that a server-like option replaces. */
73+ const assertExclusiveMode = ( option : string , hasMode : boolean , positionals : string [ ] ) : void => {
74+ if ( hasMode ) {
75+ throw new Error (
76+ `The ${ option } option cannot be used with --write, --check, or --list-different.` ,
77+ ) ;
78+ }
79+
80+ if ( positionals . length > 0 ) {
81+ throw new Error ( `The ${ option } option cannot be used with file arguments.` ) ;
82+ }
83+ } ;
84+
6985const parseFmtCLIArgs = ( args : string [ ] ) : ParsedFmtCLIArgs => {
7086 const { values, positionals } = parseArgs ( {
7187 args,
@@ -81,6 +97,7 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
8197 'with-node-modules' : { type : 'boolean' } ,
8298 'parallel-workers' : { type : 'string' } ,
8399 'stdin-filepath' : { type : 'string' } ,
100+ lsp : { type : 'boolean' } ,
84101 help : { type : 'boolean' , short : 'h' } ,
85102 } ,
86103 allowPositionals : true ,
@@ -110,19 +127,20 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
110127 const maxWorkers = parseMaxWorkers ( parallelWorkers ) ;
111128 const help = values . help ?? false ;
112129 const stdinFilepath = values . stdinFilepath ;
130+ const lsp = values . lsp ?? false ;
113131
114- if ( stdinFilepath !== undefined ) {
115- if ( modes . length > 0 ) {
116- throw new Error (
117- 'The --stdin-filepath option cannot be used with --write, --check, or --list-different.' ,
118- ) ;
119- }
132+ if ( lsp ) {
133+ assertExclusiveMode ( '--lsp' , modes . length > 0 , positionals ) ;
120134
121- if ( positionals . length > 0 ) {
122- throw new Error ( 'The --stdin-filepath option cannot be used with file arguments .' ) ;
135+ if ( stdinFilepath !== undefined ) {
136+ throw new Error ( 'The --lsp option cannot be used with --stdin-filepath .' ) ;
123137 }
124138 }
125139
140+ if ( stdinFilepath !== undefined ) {
141+ assertExclusiveMode ( '--stdin-filepath' , modes . length > 0 , positionals ) ;
142+ }
143+
126144 return {
127145 cache,
128146 cacheLocation,
@@ -135,6 +153,7 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
135153 maxWorkers,
136154 help,
137155 stdinFilepath,
156+ lsp,
138157 } ;
139158} ;
140159
@@ -244,7 +263,7 @@ const logFmtResult = (
244263} ;
245264
246265const loadFmtConfig = async ( cwd : string ) : Promise < ResolvedFmtConfig > => {
247- const { configs, filePath } = await loadRstackConfig ( ) ;
266+ const { configs, filePath } = await loadRstackConfig ( { cwd } ) ;
248267
249268 return resolveFmtConfig ( {
250269 definition : configs . fmt ,
@@ -266,6 +285,7 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
266285 help,
267286 ignorePaths,
268287 ignoreUnknown,
288+ lsp,
269289 maxWorkers,
270290 mode,
271291 noErrorOnUnmatchedPattern,
@@ -278,6 +298,23 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
278298 return ;
279299 }
280300
301+ if ( lsp ) {
302+ const { runFmtLsp } = await import (
303+ /* rspackChunkName: 'fmtLsp' */
304+ './lsp/server.ts'
305+ ) ;
306+ await runFmtLsp ( {
307+ // The client's workspace root is not necessarily the directory the
308+ // editor spawned the server in; the server resolves relative
309+ // `--ignore-path` values from this cwd so they stay based on the same
310+ // directory as a relative `--config`.
311+ cwd,
312+ ignorePaths,
313+ loadConfig : loadFmtConfig ,
314+ } ) ;
315+ return ;
316+ }
317+
281318 if ( stdinFilepath !== undefined ) {
282319 const { runFmtStdin } = await import (
283320 /* rspackChunkName: 'fmtStdin' */
0 commit comments