@@ -380,7 +380,7 @@ namespace ts.server {
380380 // correct results to all other projects.
381381
382382 const defaultProjectResults = perProjectResults . get ( defaultProject ) ;
383- if ( defaultProjectResults ?. [ 0 ] . references [ 0 ] ?. isDefinition === undefined ) {
383+ if ( defaultProjectResults ?. [ 0 ] ? .references [ 0 ] ?. isDefinition === undefined ) {
384384 // Clear all isDefinition properties
385385 perProjectResults . forEach ( projectResults => {
386386 for ( const referencedSymbol of projectResults ) {
@@ -531,26 +531,35 @@ namespace ts.server {
531531 defaultDefinition :
532532 defaultProject . getLanguageService ( ) . getSourceMapper ( ) . tryGetSourcePosition ( defaultDefinition ! ) ) ;
533533
534- // Track which projects we have already searched so that we don't repeat searches.
535- // We store the project key, rather than the project, because that's what `loadAncestorProjectTree` wants.
536- // (For that same reason, we don't use `resultsMap` for this check.)
537- const searchedProjects = new Set < string > ( ) ;
534+ // The keys of resultsMap allow us to check which projects have already been searched, but we also
535+ // maintain a set of strings because that's what `loadAncestorProjectTree` wants.
536+ const searchedProjectKeys = new Set < string > ( ) ;
538537
539538 onCancellation:
540539 while ( queue . length ) {
541540 while ( queue . length ) {
542541 if ( cancellationToken . isCancellationRequested ( ) ) break onCancellation;
543542
543+ let skipCount = 0 ;
544+ for ( ; skipCount < queue . length && resultsMap . has ( queue [ skipCount ] . project ) ; skipCount ++ ) ;
545+
546+ if ( skipCount === queue . length ) {
547+ queue . length = 0 ;
548+ break ;
549+ }
550+
551+ if ( skipCount > 0 ) {
552+ queue . splice ( 0 , skipCount ) ;
553+ }
554+
555+ // NB: we may still skip if it's a project reference redirect
544556 const { project, location } = queue . shift ( ) ! ;
545557
546558 if ( isLocationProjectReferenceRedirect ( project , location ) ) continue ;
547559
548- if ( ! tryAddToSet ( searchedProjects , getProjectKey ( project ) ) ) continue ;
549-
550560 const projectResults = searchPosition ( project , location ) ;
551- if ( projectResults ) {
552- resultsMap . set ( project , projectResults ) ;
553- }
561+ resultsMap . set ( project , projectResults ?? emptyArray ) ;
562+ searchedProjectKeys . add ( getProjectKey ( project ) ) ;
554563 }
555564
556565 // At this point, we know about all projects passed in as arguments and any projects in which
@@ -559,10 +568,10 @@ namespace ts.server {
559568 // containing `initialLocation`.
560569 if ( defaultDefinition ) {
561570 // This seems to mean "load all projects downstream from any member of `seenProjects`".
562- projectService . loadAncestorProjectTree ( searchedProjects ) ;
571+ projectService . loadAncestorProjectTree ( searchedProjectKeys ) ;
563572 projectService . forEachEnabledProject ( project => {
564573 if ( cancellationToken . isCancellationRequested ( ) ) return ; // There's no mechanism for skipping the remaining projects
565- if ( searchedProjects . has ( getProjectKey ( project ) ) ) return ; // Can loop forever without this (enqueue here, dequeue above, repeat)
574+ if ( resultsMap . has ( project ) ) return ; // Can loop forever without this (enqueue here, dequeue above, repeat)
566575 const location = mapDefinitionInProject ( defaultDefinition , project , getGeneratedDefinition , getSourceDefinition ) ;
567576 if ( location ) {
568577 queue . push ( { project, location } ) ;
@@ -573,9 +582,10 @@ namespace ts.server {
573582
574583 // In the common case where there's only one project, return a simpler result to make
575584 // it easier for the caller to skip post-processing.
576- if ( searchedProjects . size === 1 ) {
585+ if ( resultsMap . size === 1 ) {
577586 const it = resultsMap . values ( ) . next ( ) ;
578- return it . done ? emptyArray : it . value ; // There may not be any results at all
587+ Debug . assert ( ! it . done ) ;
588+ return it . value ;
579589 }
580590
581591 return resultsMap ;
@@ -593,7 +603,7 @@ namespace ts.server {
593603 const originalScriptInfo = projectService . getScriptInfo ( originalLocation . fileName ) ! ;
594604
595605 for ( const project of originalScriptInfo . containingProjects ) {
596- if ( ! project . isOrphan ( ) ) {
606+ if ( ! project . isOrphan ( ) && ! resultsMap . has ( project ) ) { // Optimization: don't enqueue if will be discarded
597607 queue . push ( { project, location : originalLocation } ) ;
598608 }
599609 }
@@ -602,7 +612,7 @@ namespace ts.server {
602612 if ( symlinkedProjectsMap ) {
603613 symlinkedProjectsMap . forEach ( ( symlinkedProjects , symlinkedPath ) => {
604614 for ( const symlinkedProject of symlinkedProjects ) {
605- if ( ! symlinkedProject . isOrphan ( ) ) {
615+ if ( ! symlinkedProject . isOrphan ( ) && ! resultsMap . has ( symlinkedProject ) ) { // Optimization: don't enqueue if will be discarded
606616 queue . push ( { project : symlinkedProject , location : { fileName : symlinkedPath as string , pos : originalLocation . pos } } ) ;
607617 }
608618 }
0 commit comments