@@ -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 [ '-h, --help' , 'Display this help message' ] ,
5053 ] ,
5154 } ,
@@ -65,6 +68,19 @@ const parseMaxWorkers = (value: string | undefined): number | undefined => {
6568 return maxWorkers ;
6669} ;
6770
71+ /** Rejects the mode flags and file arguments that a server-like option replaces. */
72+ const assertExclusiveMode = ( option : string , hasMode : boolean , positionals : string [ ] ) : void => {
73+ if ( hasMode ) {
74+ throw new Error (
75+ `The ${ option } option cannot be used with --write, --check, or --list-different.` ,
76+ ) ;
77+ }
78+
79+ if ( positionals . length > 0 ) {
80+ throw new Error ( `The ${ option } option cannot be used with file arguments.` ) ;
81+ }
82+ } ;
83+
6884const parseFmtCLIArgs = ( args : string [ ] ) : ParsedFmtCLIArgs => {
6985 const { values, positionals } = parseArgs ( {
7086 args,
@@ -80,6 +96,7 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
8096 'with-node-modules' : { type : 'boolean' } ,
8197 'parallel-workers' : { type : 'string' } ,
8298 'stdin-filepath' : { type : 'string' } ,
99+ lsp : { type : 'boolean' } ,
83100 help : { type : 'boolean' , short : 'h' } ,
84101 } ,
85102 allowPositionals : true ,
@@ -109,19 +126,20 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
109126 const maxWorkers = parseMaxWorkers ( parallelWorkers ) ;
110127 const help = values . help ?? false ;
111128 const stdinFilepath = values . stdinFilepath ;
129+ const lsp = values . lsp ?? false ;
112130
113- if ( stdinFilepath !== undefined ) {
114- if ( modes . length > 0 ) {
115- throw new Error (
116- 'The --stdin-filepath option cannot be used with --write, --check, or --list-different.' ,
117- ) ;
118- }
131+ if ( lsp ) {
132+ assertExclusiveMode ( '--lsp' , modes . length > 0 , positionals ) ;
119133
120- if ( positionals . length > 0 ) {
121- throw new Error ( 'The --stdin-filepath option cannot be used with file arguments .' ) ;
134+ if ( stdinFilepath !== undefined ) {
135+ throw new Error ( 'The --lsp option cannot be used with --stdin-filepath .' ) ;
122136 }
123137 }
124138
139+ if ( stdinFilepath !== undefined ) {
140+ assertExclusiveMode ( '--stdin-filepath' , modes . length > 0 , positionals ) ;
141+ }
142+
125143 return {
126144 cache,
127145 cacheLocation,
@@ -134,6 +152,7 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => {
134152 maxWorkers,
135153 help,
136154 stdinFilepath,
155+ lsp,
137156 } ;
138157} ;
139158
@@ -243,7 +262,7 @@ const logFmtResult = (
243262} ;
244263
245264const loadFmtConfig = async ( cwd : string ) : Promise < ResolvedFmtConfig > => {
246- const { configs, filePath } = await loadRstackConfig ( ) ;
265+ const { configs, filePath } = await loadRstackConfig ( { cwd } ) ;
247266
248267 return resolveFmtConfig ( {
249268 definition : configs . fmt ,
@@ -265,6 +284,7 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
265284 help,
266285 ignorePaths,
267286 ignoreUnknown,
287+ lsp,
268288 maxWorkers,
269289 mode,
270290 noErrorOnUnmatchedPattern,
@@ -277,6 +297,23 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
277297 return ;
278298 }
279299
300+ if ( lsp ) {
301+ const { runFmtLsp } = await import (
302+ /* rspackChunkName: 'fmtLsp' */
303+ './lsp/server.ts'
304+ ) ;
305+ await runFmtLsp ( {
306+ // The client's workspace root is not necessarily the directory the
307+ // editor spawned the server in; the server resolves relative
308+ // `--ignore-path` values from this cwd so they stay based on the same
309+ // directory as a relative `--config`.
310+ cwd,
311+ ignorePaths,
312+ loadConfig : loadFmtConfig ,
313+ } ) ;
314+ return ;
315+ }
316+
280317 if ( stdinFilepath !== undefined ) {
281318 const { runFmtStdin } = await import (
282319 /* rspackChunkName: 'fmtStdin' */
0 commit comments