@@ -799,30 +799,34 @@ export namespace ESLint {
799799 'javascript' , 'javascriptreact'
800800 ] ) ;
801801
802- const projectFolderIndicators : {
803- fileName : string ;
804- isRoot : boolean ;
805- isFlatConfig : boolean ;
806- } [ ] = [
807- { fileName : 'eslint.config.js' , isRoot : true , isFlatConfig : true } ,
808- { fileName : 'eslint.config.cjs' , isRoot : true , isFlatConfig : true } ,
809- { fileName : 'eslint.config.mjs' , isRoot : true , isFlatConfig : true } ,
810- { fileName : 'eslint.config.ts' , isRoot : true , isFlatConfig : true } ,
811- { fileName : 'eslint.config.cts' , isRoot : true , isFlatConfig : true } ,
812- { fileName : 'eslint.config.mts' , isRoot : true , isFlatConfig : true } ,
813- { fileName : 'package-lock.json' , isRoot : true , isFlatConfig : false } ,
814- { fileName : 'yarn.lock' , isRoot : true , isFlatConfig : false } ,
815- { fileName : 'pnpm-lock.yaml' , isRoot : true , isFlatConfig : false } ,
816- { fileName : 'npm-shrinkwrap.json' , isRoot : true , isFlatConfig : false } ,
817- { fileName : 'bun.lockb' , isRoot : true , isFlatConfig : false } ,
818- { fileName : 'bun.lock' , isRoot : true , isFlatConfig : false } ,
819- { fileName : 'package.json' , isRoot : false , isFlatConfig : false } ,
820- { fileName : '.eslintignore' , isRoot : true , isFlatConfig : false } ,
821- { fileName : '.eslintrc' , isRoot : false , isFlatConfig : false } ,
822- { fileName : '.eslintrc.json' , isRoot : false , isFlatConfig : false } ,
823- { fileName : '.eslintrc.js' , isRoot : false , isFlatConfig : false } ,
824- { fileName : '.eslintrc.yaml' , isRoot : false , isFlatConfig : false } ,
825- { fileName : '.eslintrc.yml' , isRoot : false , isFlatConfig : false }
802+ const flatConfigFiles = [
803+ 'eslint.config.js' ,
804+ 'eslint.config.cjs' ,
805+ 'eslint.config.mjs' ,
806+ 'eslint.config.ts' ,
807+ 'eslint.config.cts' ,
808+ 'eslint.config.mts'
809+ ] ;
810+
811+ const legacyConfigFiles = [
812+ '.eslintrc' ,
813+ '.eslintrc.json' ,
814+ '.eslintrc.js' ,
815+ '.eslintrc.yaml' ,
816+ '.eslintrc.yml'
817+ ] ;
818+
819+ const lockfileAndWorkspaceFiles = [
820+ 'package-lock.json' ,
821+ 'yarn.lock' ,
822+ 'pnpm-lock.yaml' ,
823+ 'npm-shrinkwrap.json' ,
824+ 'bun.lockb' ,
825+ 'pnpm-workspace.yaml' ,
826+ '.yarnrc.yml' ,
827+ 'rush.json' ,
828+ 'nx.json' ,
829+ 'lerna.json'
826830 ] ;
827831
828832 const path2Library : Map < string , ESLintModule > = new Map < string , ESLintModule > ( ) ;
@@ -1296,6 +1300,104 @@ export namespace ESLint {
12961300 }
12971301 }
12981302
1303+ interface DirectoryIndicators {
1304+ directory : string ;
1305+ flatConfigs : string [ ] ;
1306+ legacyConfigs : string [ ] ;
1307+ lockfiles : string [ ] ;
1308+ hasPackageJson : boolean ;
1309+ }
1310+
1311+ function collectProjectIndicators ( directory : string ) : DirectoryIndicators {
1312+ const indicators : DirectoryIndicators = {
1313+ directory,
1314+ flatConfigs : [ ] ,
1315+ legacyConfigs : [ ] ,
1316+ lockfiles : [ ] ,
1317+ hasPackageJson : false
1318+ } ;
1319+
1320+ for ( const fileName of flatConfigFiles ) {
1321+ if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1322+ indicators . flatConfigs . push ( fileName ) ;
1323+ }
1324+ }
1325+
1326+ for ( const fileName of legacyConfigFiles ) {
1327+ if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1328+ indicators . legacyConfigs . push ( fileName ) ;
1329+ }
1330+ }
1331+
1332+ for ( const fileName of lockfileAndWorkspaceFiles ) {
1333+ if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1334+ indicators . lockfiles . push ( fileName ) ;
1335+ }
1336+ }
1337+
1338+ if ( fs . existsSync ( path . join ( directory , 'package.json' ) ) ) {
1339+ indicators . hasPackageJson = true ;
1340+ }
1341+
1342+ return indicators ;
1343+ }
1344+
1345+ function traverseUpwards ( startDirectory : string , workspaceFolder : string ) : DirectoryIndicators [ ] {
1346+ const candidates : DirectoryIndicators [ ] = [ ] ;
1347+ let directory : string | undefined = startDirectory ;
1348+
1349+ while ( directory !== undefined && directory . startsWith ( workspaceFolder ) ) {
1350+ const indicators = collectProjectIndicators ( directory ) ;
1351+ candidates . push ( indicators ) ;
1352+
1353+ const parent = path . dirname ( directory ) ;
1354+ directory = parent !== directory ? parent : undefined ;
1355+ }
1356+
1357+ return candidates ;
1358+ }
1359+
1360+ function selectWorkingDirectory ( candidates : DirectoryIndicators [ ] , workspaceFolder : string ) : [ string , boolean ] {
1361+ const lockfileRoot = candidates . find ( c => c . lockfiles . length > 0 ) ;
1362+
1363+ const nearestFlatConfig = candidates . find ( c => c . flatConfigs . length > 0 ) ;
1364+
1365+ // Find flat config at or above lockfile root (if lockfile exists)
1366+ const flatConfigAtOrAboveLockfile = lockfileRoot
1367+ ? candidates . slice ( candidates . indexOf ( lockfileRoot ) ) . find ( c => c . flatConfigs . length > 0 )
1368+ : undefined ;
1369+
1370+ const uppermostPackageJson = [ ...candidates ] . reverse ( ) . find ( c => c . hasPackageJson ) ;
1371+
1372+ // Priority 1: Flat config at or above lockfile root (best practice)
1373+ if ( lockfileRoot && flatConfigAtOrAboveLockfile ) {
1374+ return [ flatConfigAtOrAboveLockfile . directory , true ] ;
1375+ }
1376+
1377+ // Priority 2: Lockfile root with legacy config
1378+ if ( lockfileRoot && lockfileRoot . legacyConfigs . length > 0 ) {
1379+ return [ lockfileRoot . directory , false ] ;
1380+ }
1381+
1382+ // Priority 3: Lockfile root (dependency boundary)
1383+ if ( lockfileRoot ) {
1384+ return [ lockfileRoot . directory , false ] ;
1385+ }
1386+
1387+ // Priority 4: Any flat config (if no lockfile structure)
1388+ if ( nearestFlatConfig ) {
1389+ return [ nearestFlatConfig . directory , true ] ;
1390+ }
1391+
1392+ // Priority 5: Uppermost package.json (fallback for non-lockfile projects)
1393+ if ( uppermostPackageJson ) {
1394+ return [ uppermostPackageJson . directory , false ] ;
1395+ }
1396+
1397+ // Priority 6: Workspace folder
1398+ return [ workspaceFolder , false ] ;
1399+ }
1400+
12991401 export function findWorkingDirectory ( workspaceFolder : string , file : string | undefined ) : [ string , boolean ] {
13001402 if ( file === undefined || isUNC ( file ) ) {
13011403 return [ workspaceFolder , false ] ;
@@ -1305,25 +1407,9 @@ export namespace ESLint {
13051407 return [ workspaceFolder , false ] ;
13061408 }
13071409
1308- let result : string = workspaceFolder ;
1309- let flatConfig : boolean = false ;
1310- let directory : string | undefined = path . dirname ( file ) ;
1311- outer: while ( directory !== undefined && directory . startsWith ( workspaceFolder ) ) {
1312- for ( const { fileName, isRoot, isFlatConfig } of projectFolderIndicators ) {
1313- if ( fs . existsSync ( path . join ( directory , fileName ) ) ) {
1314- result = directory ;
1315- flatConfig = isFlatConfig ;
1316- if ( isRoot ) {
1317- break outer;
1318- } else {
1319- break ;
1320- }
1321- }
1322- }
1323- const parent = path . dirname ( directory ) ;
1324- directory = parent !== directory ? parent : undefined ;
1325- }
1326- return [ result , flatConfig ] ;
1410+ const startDirectory = path . dirname ( file ) ;
1411+ const candidates = traverseUpwards ( startDirectory , workspaceFolder ) ;
1412+ return selectWorkingDirectory ( candidates , workspaceFolder ) ;
13271413 }
13281414
13291415 export namespace ErrorHandlers {
0 commit comments