@@ -4,7 +4,11 @@ import { createRequire } from "node:module";
44import os from "node:os" ;
55import path from "node:path" ;
66import { fileURLToPath } from "node:url" ;
7- import { ERROR_PREVIEW_LENGTH_CHARS , JSX_FILE_PATTERN } from "../constants.js" ;
7+ import {
8+ ERROR_PREVIEW_LENGTH_CHARS ,
9+ JSX_FILE_PATTERN ,
10+ SPAWN_ARGS_MAX_LENGTH_CHARS ,
11+ } from "../constants.js" ;
812import { createOxlintConfig } from "../oxlint-config.js" ;
913import type { CleanedDiagnostic , Diagnostic , Framework , OxlintOutput } from "../types.js" ;
1014import { neutralizeDisableDirectives } from "./neutralize-disable-directives.js" ;
@@ -252,6 +256,93 @@ const resolveDiagnosticCategory = (plugin: string, rule: string): string => {
252256 return RULE_CATEGORY_MAP [ ruleKey ] ?? PLUGIN_CATEGORY_MAP [ plugin ] ?? "Other" ;
253257} ;
254258
259+ const estimateArgsLength = ( args : string [ ] ) : number =>
260+ args . reduce ( ( total , argument ) => total + argument . length + 1 , 0 ) ;
261+
262+ const batchIncludePaths = ( baseArgs : string [ ] , includePaths : string [ ] ) : string [ ] [ ] => {
263+ const baseArgsLength = estimateArgsLength ( baseArgs ) ;
264+ const batches : string [ ] [ ] = [ ] ;
265+ let currentBatch : string [ ] = [ ] ;
266+ let currentBatchLength = baseArgsLength ;
267+
268+ for ( const filePath of includePaths ) {
269+ const entryLength = filePath . length + 1 ;
270+ if ( currentBatch . length > 0 && currentBatchLength + entryLength > SPAWN_ARGS_MAX_LENGTH_CHARS ) {
271+ batches . push ( currentBatch ) ;
272+ currentBatch = [ ] ;
273+ currentBatchLength = baseArgsLength ;
274+ }
275+ currentBatch . push ( filePath ) ;
276+ currentBatchLength += entryLength ;
277+ }
278+
279+ if ( currentBatch . length > 0 ) {
280+ batches . push ( currentBatch ) ;
281+ }
282+
283+ return batches ;
284+ } ;
285+
286+ const spawnOxlint = ( args : string [ ] , rootDirectory : string ) : Promise < string > =>
287+ new Promise < string > ( ( resolve , reject ) => {
288+ const child = spawn ( process . execPath , args , {
289+ cwd : rootDirectory ,
290+ } ) ;
291+
292+ const stdoutBuffers : Buffer [ ] = [ ] ;
293+ const stderrBuffers : Buffer [ ] = [ ] ;
294+
295+ child . stdout . on ( "data" , ( buffer : Buffer ) => stdoutBuffers . push ( buffer ) ) ;
296+ child . stderr . on ( "data" , ( buffer : Buffer ) => stderrBuffers . push ( buffer ) ) ;
297+
298+ child . on ( "error" , ( error ) => reject ( new Error ( `Failed to run oxlint: ${ error . message } ` ) ) ) ;
299+ child . on ( "close" , ( ) => {
300+ const output = Buffer . concat ( stdoutBuffers ) . toString ( "utf-8" ) . trim ( ) ;
301+ if ( ! output ) {
302+ const stderrOutput = Buffer . concat ( stderrBuffers ) . toString ( "utf-8" ) . trim ( ) ;
303+ if ( stderrOutput ) {
304+ reject ( new Error ( `Failed to run oxlint: ${ stderrOutput } ` ) ) ;
305+ return ;
306+ }
307+ }
308+ resolve ( output ) ;
309+ } ) ;
310+ } ) ;
311+
312+ const parseOxlintOutput = ( stdout : string ) : Diagnostic [ ] => {
313+ if ( ! stdout ) return [ ] ;
314+
315+ let output : OxlintOutput ;
316+ try {
317+ output = JSON . parse ( stdout ) as OxlintOutput ;
318+ } catch {
319+ throw new Error (
320+ `Failed to parse oxlint output: ${ stdout . slice ( 0 , ERROR_PREVIEW_LENGTH_CHARS ) } ` ,
321+ ) ;
322+ }
323+
324+ return output . diagnostics
325+ . filter ( ( diagnostic ) => diagnostic . code && JSX_FILE_PATTERN . test ( diagnostic . filename ) )
326+ . map ( ( diagnostic ) => {
327+ const { plugin, rule } = parseRuleCode ( diagnostic . code ) ;
328+ const primaryLabel = diagnostic . labels [ 0 ] ;
329+
330+ const cleaned = cleanDiagnosticMessage ( diagnostic . message , diagnostic . help , plugin , rule ) ;
331+
332+ return {
333+ filePath : diagnostic . filename ,
334+ plugin,
335+ rule,
336+ severity : diagnostic . severity ,
337+ message : cleaned . message ,
338+ help : cleaned . help ,
339+ line : primaryLabel ?. span . line ?? 0 ,
340+ column : primaryLabel ?. span . column ?? 0 ,
341+ category : resolveDiagnosticCategory ( plugin , rule ) ,
342+ } ;
343+ } ) ;
344+ } ;
345+
255346export const runOxlint = async (
256347 rootDirectory : string ,
257348 hasTypeScript : boolean ,
@@ -272,76 +363,23 @@ export const runOxlint = async (
272363 fs . writeFileSync ( configPath , JSON . stringify ( config , null , 2 ) ) ;
273364
274365 const oxlintBinary = resolveOxlintBinary ( ) ;
275- const args = [ oxlintBinary , "-c" , configPath , "--format" , "json" ] ;
366+ const baseArgs = [ oxlintBinary , "-c" , configPath , "--format" , "json" ] ;
276367
277368 if ( hasTypeScript ) {
278- args . push ( "--tsconfig" , "./tsconfig.json" ) ;
369+ baseArgs . push ( "--tsconfig" , "./tsconfig.json" ) ;
279370 }
280371
281- if ( includePaths !== undefined ) {
282- args . push ( ...includePaths ) ;
283- } else {
284- args . push ( "." ) ;
285- }
286-
287- const stdout = await new Promise < string > ( ( resolve , reject ) => {
288- const child = spawn ( process . execPath , args , {
289- cwd : rootDirectory ,
290- } ) ;
291-
292- const stdoutBuffers : Buffer [ ] = [ ] ;
293- const stderrBuffers : Buffer [ ] = [ ] ;
294-
295- child . stdout . on ( "data" , ( buffer : Buffer ) => stdoutBuffers . push ( buffer ) ) ;
296- child . stderr . on ( "data" , ( buffer : Buffer ) => stderrBuffers . push ( buffer ) ) ;
297-
298- child . on ( "error" , ( error ) => reject ( new Error ( `Failed to run oxlint: ${ error . message } ` ) ) ) ;
299- child . on ( "close" , ( ) => {
300- const output = Buffer . concat ( stdoutBuffers ) . toString ( "utf-8" ) . trim ( ) ;
301- if ( ! output ) {
302- const stderrOutput = Buffer . concat ( stderrBuffers ) . toString ( "utf-8" ) . trim ( ) ;
303- if ( stderrOutput ) {
304- reject ( new Error ( `Failed to run oxlint: ${ stderrOutput } ` ) ) ;
305- return ;
306- }
307- }
308- resolve ( output ) ;
309- } ) ;
310- } ) ;
311-
312- if ( ! stdout ) {
313- return [ ] ;
314- }
372+ const fileBatches =
373+ includePaths !== undefined ? batchIncludePaths ( baseArgs , includePaths ) : [ [ "." ] ] ;
315374
316- let output : OxlintOutput ;
317- try {
318- output = JSON . parse ( stdout ) as OxlintOutput ;
319- } catch {
320- throw new Error (
321- `Failed to parse oxlint output: ${ stdout . slice ( 0 , ERROR_PREVIEW_LENGTH_CHARS ) } ` ,
322- ) ;
375+ const allDiagnostics : Diagnostic [ ] = [ ] ;
376+ for ( const batch of fileBatches ) {
377+ const batchArgs = [ ...baseArgs , ...batch ] ;
378+ const stdout = await spawnOxlint ( batchArgs , rootDirectory ) ;
379+ allDiagnostics . push ( ...parseOxlintOutput ( stdout ) ) ;
323380 }
324381
325- return output . diagnostics
326- . filter ( ( diagnostic ) => diagnostic . code && JSX_FILE_PATTERN . test ( diagnostic . filename ) )
327- . map ( ( diagnostic ) => {
328- const { plugin, rule } = parseRuleCode ( diagnostic . code ) ;
329- const primaryLabel = diagnostic . labels [ 0 ] ;
330-
331- const cleaned = cleanDiagnosticMessage ( diagnostic . message , diagnostic . help , plugin , rule ) ;
332-
333- return {
334- filePath : diagnostic . filename ,
335- plugin,
336- rule,
337- severity : diagnostic . severity ,
338- message : cleaned . message ,
339- help : cleaned . help ,
340- line : primaryLabel ?. span . line ?? 0 ,
341- column : primaryLabel ?. span . column ?? 0 ,
342- category : resolveDiagnosticCategory ( plugin , rule ) ,
343- } ;
344- } ) ;
382+ return allDiagnostics ;
345383 } finally {
346384 restoreDisableDirectives ( ) ;
347385 if ( fs . existsSync ( configPath ) ) {
0 commit comments