@@ -294,70 +294,35 @@ namespace ts.server {
294
294
return deduplicate ( outputs , equateValues ) ;
295
295
}
296
296
297
- type CombineOutputResult < T > = { project : Project ; result : readonly T [ ] ; } [ ] ;
298
- function combineOutputResultContains < T > ( outputs : CombineOutputResult < T > , output : T , areEqual : ( a : T , b : T ) => boolean ) {
299
- return outputs . some ( ( { result } ) => contains ( result , output , areEqual ) ) ;
300
- }
301
- function addToCombineOutputResult < T > ( outputs : CombineOutputResult < T > , project : Project , result : readonly T [ ] ) {
302
- if ( result . length ) outputs . push ( { project, result } ) ;
303
- }
304
-
305
- function combineProjectOutputFromEveryProject < T > ( projectService : ProjectService , action : ( project : Project ) => readonly T [ ] , areEqual : ( a : T , b : T ) => boolean ) : CombineOutputResult < T > {
306
- const outputs : CombineOutputResult < T > = [ ] ;
307
- projectService . loadAncestorProjectTree ( ) ;
308
- projectService . forEachEnabledProject ( project => {
309
- const theseOutputs = action ( project ) ;
310
- addToCombineOutputResult ( outputs , project , filter ( theseOutputs , output => ! combineOutputResultContains ( outputs , output , areEqual ) ) ) ;
311
- } ) ;
312
- return outputs ;
313
- }
314
-
315
- function flattenCombineOutputResult < T > ( outputs : CombineOutputResult < T > ) : readonly T [ ] {
316
- return flatMap ( outputs , ( { result } ) => result ) ;
317
- }
318
-
319
- function combineProjectOutputWhileOpeningReferencedProjects < T > (
320
- projects : Projects ,
321
- defaultProject : Project ,
322
- action : ( project : Project ) => readonly T [ ] ,
323
- getLocation : ( t : T ) => DocumentPosition ,
324
- resultsEqual : ( a : T , b : T ) => boolean ,
325
- ) : CombineOutputResult < T > {
326
- const outputs : CombineOutputResult < T > = [ ] ;
327
- combineProjectOutputWorker (
328
- projects ,
329
- defaultProject ,
330
- /*initialLocation*/ undefined ,
331
- ( project , _ , tryAddToTodo ) => {
332
- const theseOutputs = action ( project ) ;
333
- addToCombineOutputResult ( outputs , project , filter ( theseOutputs , output => ! combineOutputResultContains ( outputs , output , resultsEqual ) && ! tryAddToTodo ( project , getLocation ( output ) ) ) ) ;
334
- } ,
335
- ) ;
336
- return outputs ;
337
- }
297
+ interface ProjectNavigateToItems {
298
+ project : Project ;
299
+ navigateToItems : readonly NavigateToItem [ ] ;
300
+ } ;
338
301
339
302
function combineProjectOutputForRenameLocations (
340
303
projects : Projects ,
341
304
defaultProject : Project ,
342
305
initialLocation : DocumentPosition ,
343
306
findInStrings : boolean ,
344
307
findInComments : boolean ,
345
- hostPreferences : UserPreferences
308
+ { providePrefixAndSuffixTextForRename } : UserPreferences
346
309
) : readonly RenameLocation [ ] {
347
310
const outputs : RenameLocation [ ] = [ ] ;
348
311
combineProjectOutputWorker (
349
312
projects ,
350
313
defaultProject ,
351
314
initialLocation ,
352
315
( project , location , tryAddToTodo ) => {
353
- for ( const output of project . getLanguageService ( ) . findRenameLocations ( location . fileName , location . pos , findInStrings , findInComments , hostPreferences . providePrefixAndSuffixTextForRename ) || emptyArray ) {
354
- if ( ! contains ( outputs , output , documentSpansEqual ) && ! tryAddToTodo ( project , documentSpanLocation ( output ) ) ) {
355
- outputs . push ( output ) ;
316
+ const projectOutputs = project . getLanguageService ( ) . findRenameLocations ( location . fileName , location . pos , findInStrings , findInComments , providePrefixAndSuffixTextForRename ) ;
317
+ if ( projectOutputs ) {
318
+ for ( const output of projectOutputs ) {
319
+ if ( ! contains ( outputs , output , documentSpansEqual ) && ! tryAddToTodo ( project , documentSpanLocation ( output ) ) ) {
320
+ outputs . push ( output ) ;
321
+ }
356
322
}
357
323
}
358
324
} ,
359
325
) ;
360
-
361
326
return outputs ;
362
327
}
363
328
@@ -381,27 +346,30 @@ namespace ts.server {
381
346
initialLocation ,
382
347
( project , location , getMappedLocation ) => {
383
348
logger . info ( `Finding references to ${ location . fileName } position ${ location . pos } in project ${ project . getProjectName ( ) } ` ) ;
384
- for ( const outputReferencedSymbol of project . getLanguageService ( ) . findReferences ( location . fileName , location . pos ) || emptyArray ) {
385
- const mappedDefinitionFile = getMappedLocation ( project , documentSpanLocation ( outputReferencedSymbol . definition ) ) ;
386
- const definition : ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ?
387
- outputReferencedSymbol . definition :
388
- {
389
- ...outputReferencedSymbol . definition ,
390
- textSpan : createTextSpan ( mappedDefinitionFile . pos , outputReferencedSymbol . definition . textSpan . length ) ,
391
- fileName : mappedDefinitionFile . fileName ,
392
- contextSpan : getMappedContextSpan ( outputReferencedSymbol . definition , project )
393
- } ;
394
-
395
- let symbolToAddTo = find ( outputs , o => documentSpansEqual ( o . definition , definition ) ) ;
396
- if ( ! symbolToAddTo ) {
397
- symbolToAddTo = { definition, references : [ ] } ;
398
- outputs . push ( symbolToAddTo ) ;
399
- }
349
+ const projectOutputs = project . getLanguageService ( ) . findReferences ( location . fileName , location . pos ) ;
350
+ if ( projectOutputs ) {
351
+ for ( const referencedSymbol of projectOutputs ) {
352
+ const mappedDefinitionFile = getMappedLocation ( project , documentSpanLocation ( referencedSymbol . definition ) ) ;
353
+ const definition : ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ?
354
+ referencedSymbol . definition :
355
+ {
356
+ ...referencedSymbol . definition ,
357
+ textSpan : createTextSpan ( mappedDefinitionFile . pos , referencedSymbol . definition . textSpan . length ) ,
358
+ fileName : mappedDefinitionFile . fileName ,
359
+ contextSpan : getMappedContextSpan ( referencedSymbol . definition , project )
360
+ } ;
361
+
362
+ let symbolToAddTo = find ( outputs , o => documentSpansEqual ( o . definition , definition ) ) ;
363
+ if ( ! symbolToAddTo ) {
364
+ symbolToAddTo = { definition, references : [ ] } ;
365
+ outputs . push ( symbolToAddTo ) ;
366
+ }
400
367
401
- for ( const ref of outputReferencedSymbol . references ) {
402
- // If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
403
- if ( ! contains ( symbolToAddTo . references , ref , documentSpansEqual ) && ! getMappedLocation ( project , documentSpanLocation ( ref ) ) ) {
404
- symbolToAddTo . references . push ( ref ) ;
368
+ for ( const ref of referencedSymbol . references ) {
369
+ // If it's in a mapped file, that is added to the todo list by `getMappedLocation`.
370
+ if ( ! contains ( symbolToAddTo . references , ref , documentSpansEqual ) && ! getMappedLocation ( project , documentSpanLocation ( ref ) ) ) {
371
+ symbolToAddTo . references . push ( ref ) ;
372
+ }
405
373
}
406
374
}
407
375
}
@@ -411,29 +379,6 @@ namespace ts.server {
411
379
return outputs . filter ( o => o . references . length !== 0 ) ;
412
380
}
413
381
414
- function combineProjectOutputForFileReferences (
415
- projects : Projects ,
416
- defaultProject : Project ,
417
- fileName : string
418
- ) : readonly ReferenceEntry [ ] {
419
- const outputs : ReferenceEntry [ ] = [ ] ;
420
-
421
- combineProjectOutputWorker (
422
- projects ,
423
- defaultProject ,
424
- /*initialLocation*/ undefined ,
425
- project => {
426
- for ( const referenceEntry of project . getLanguageService ( ) . getFileReferences ( fileName ) || emptyArray ) {
427
- if ( ! contains ( outputs , referenceEntry , documentSpansEqual ) ) {
428
- outputs . push ( referenceEntry ) ;
429
- }
430
- }
431
- } ,
432
- ) ;
433
-
434
- return outputs ;
435
- }
436
-
437
382
interface ProjectAndLocation < TLocation extends DocumentPosition | undefined > {
438
383
readonly project : Project ;
439
384
readonly location : TLocation ;
@@ -1610,11 +1555,22 @@ namespace ts.server {
1610
1555
1611
1556
private getFileReferences ( args : protocol . FileRequestArgs , simplifiedResult : boolean ) : protocol . FileReferencesResponseBody | readonly ReferenceEntry [ ] {
1612
1557
const projects = this . getProjects ( args ) ;
1613
- const references = combineProjectOutputForFileReferences (
1614
- projects ,
1615
- this . getDefaultProject ( args ) ,
1616
- args . file ,
1617
- ) ;
1558
+ const fileName = args . file ;
1559
+
1560
+ const references : ReferenceEntry [ ] = [ ] ;
1561
+
1562
+ forEachProjectInProjects ( projects , /*path*/ undefined , project => {
1563
+ if ( project . getCancellationToken ( ) . isCancellationRequested ( ) ) return ;
1564
+
1565
+ const projectOutputs = project . getLanguageService ( ) . getFileReferences ( fileName ) ;
1566
+ if ( projectOutputs ) {
1567
+ for ( const referenceEntry of projectOutputs ) {
1568
+ if ( ! contains ( references , referenceEntry , documentSpansEqual ) ) {
1569
+ references . push ( referenceEntry ) ;
1570
+ }
1571
+ }
1572
+ }
1573
+ } ) ;
1618
1574
1619
1575
if ( ! simplifiedResult ) return references ;
1620
1576
const refs = references . map ( entry => referenceEntryToReferencesResponseItem ( this . projectService , entry ) ) ;
@@ -2092,10 +2048,10 @@ namespace ts.server {
2092
2048
private getNavigateToItems ( args : protocol . NavtoRequestArgs , simplifiedResult : boolean ) : readonly protocol . NavtoItem [ ] | readonly NavigateToItem [ ] {
2093
2049
const full = this . getFullNavigateToItems ( args ) ;
2094
2050
return ! simplifiedResult ?
2095
- flattenCombineOutputResult ( full ) :
2051
+ flatMap ( full , ( { navigateToItems } ) => navigateToItems ) :
2096
2052
flatMap (
2097
2053
full ,
2098
- ( { project, result } ) => result . map ( navItem => {
2054
+ ( { project, navigateToItems } ) => navigateToItems . map ( navItem => {
2099
2055
const scriptInfo = project . getScriptInfo ( navItem . fileName ) ! ;
2100
2056
const bakedItem : protocol . NavtoItem = {
2101
2057
name : navItem . name ,
@@ -2121,26 +2077,72 @@ namespace ts.server {
2121
2077
) ;
2122
2078
}
2123
2079
2124
- private getFullNavigateToItems ( args : protocol . NavtoRequestArgs ) : CombineOutputResult < NavigateToItem > {
2080
+ private getFullNavigateToItems ( args : protocol . NavtoRequestArgs ) : ProjectNavigateToItems [ ] {
2125
2081
const { currentFileOnly, searchValue, maxResultCount, projectFileName } = args ;
2082
+
2126
2083
if ( currentFileOnly ) {
2127
2084
Debug . assertIsDefined ( args . file ) ;
2128
2085
const { file, project } = this . getFileAndProject ( args as protocol . FileRequestArgs ) ;
2129
- return [ { project, result : project . getLanguageService ( ) . getNavigateToItems ( searchValue , maxResultCount , file ) } ] ;
2130
- }
2131
- else if ( ! args . file && ! projectFileName ) {
2132
- return combineProjectOutputFromEveryProject (
2133
- this . projectService ,
2134
- project => project . getLanguageService ( ) . getNavigateToItems ( searchValue , maxResultCount , /*filename*/ undefined , /*excludeDts*/ project . isNonTsProject ( ) ) ,
2135
- navigateToItemIsEqualTo ) ;
2136
- }
2137
- const fileArgs = args as protocol . FileRequestArgs ;
2138
- return combineProjectOutputWhileOpeningReferencedProjects < NavigateToItem > (
2139
- this . getProjects ( fileArgs ) ,
2140
- this . getDefaultProject ( fileArgs ) ,
2141
- project => project . getLanguageService ( ) . getNavigateToItems ( searchValue , maxResultCount , /*fileName*/ undefined , /*excludeDts*/ project . isNonTsProject ( ) ) ,
2142
- documentSpanLocation ,
2143
- navigateToItemIsEqualTo ) ;
2086
+ return [ { project, navigateToItems : project . getLanguageService ( ) . getNavigateToItems ( searchValue , maxResultCount , file ) } ] ;
2087
+ }
2088
+
2089
+ const outputs : ProjectNavigateToItems [ ] = [ ] ;
2090
+
2091
+ // This is effectively a hashset with `name` as the custom hash and `navigateToItemIsEqualTo` as the custom equals.
2092
+ // `name` is a very cheap hash function, but we could incorporate other properties to reduce collisions.
2093
+ const seenItems = new Map < string , NavigateToItem [ ] > ( ) ; // name to items with that name
2094
+
2095
+ if ( ! args . file && ! projectFileName ) {
2096
+ // VS Code's `Go to symbol in workspaces` sends request like this
2097
+
2098
+ // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2099
+ // This appears to have been intended to search all projects but, in practice, it seems to only search
2100
+ // those that are downstream from already-loaded projects.
2101
+ // Filtering by !isSourceOfProjectReferenceRedirect is new, but seems appropriate and consistent with
2102
+ // the case below.
2103
+ this . projectService . loadAncestorProjectTree ( ) ;
2104
+ this . projectService . forEachEnabledProject ( project => addItemsForProject ( project ) ) ;
2105
+ }
2106
+ else {
2107
+ // VS's `Go to symbol` sends requests with just a project and doesn't want cascading since it will
2108
+ // send a separate request for each project of interest
2109
+
2110
+ // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2111
+ // This doesn't really make sense unless it's a single project matching `projectFileName`
2112
+ const projects = this . getProjects ( args as protocol . FileRequestArgs ) ;
2113
+ forEachProjectInProjects ( projects , /*path*/ undefined , project => addItemsForProject ( project ) ) ;
2114
+ }
2115
+
2116
+ return outputs ;
2117
+
2118
+ // Mutates `outputs`
2119
+ function addItemsForProject ( project : Project ) {
2120
+ const projectItems = project . getLanguageService ( ) . getNavigateToItems ( searchValue , maxResultCount , /*filename*/ undefined , /*excludeDts*/ project . isNonTsProject ( ) ) ;
2121
+ const unseenItems = filter ( projectItems , item => tryAddSeenItem ( item ) && ! getMappedLocation ( documentSpanLocation ( item ) , project ) ) ;
2122
+ if ( unseenItems . length ) {
2123
+ outputs . push ( { project, navigateToItems : unseenItems } ) ;
2124
+ }
2125
+ }
2126
+
2127
+ // Returns true if the item had not been seen before
2128
+ // Mutates `seenItems`
2129
+ function tryAddSeenItem ( item : NavigateToItem ) {
2130
+ const name = item . name ;
2131
+ if ( ! seenItems . has ( name ) ) {
2132
+ seenItems . set ( name , [ item ] ) ;
2133
+ return true ;
2134
+ }
2135
+
2136
+ const seen = seenItems . get ( name ) ! ;
2137
+ for ( const seenItem of seen ) {
2138
+ if ( navigateToItemIsEqualTo ( seenItem , item ) ) {
2139
+ return false ;
2140
+ }
2141
+ }
2142
+
2143
+ seen . push ( item ) ;
2144
+ return true ;
2145
+ }
2144
2146
2145
2147
function navigateToItemIsEqualTo ( a : NavigateToItem , b : NavigateToItem ) : boolean {
2146
2148
if ( a === b ) {
@@ -2255,14 +2257,29 @@ namespace ts.server {
2255
2257
const newPath = toNormalizedPath ( args . newFilePath ) ;
2256
2258
const formatOptions = this . getHostFormatOptions ( ) ;
2257
2259
const preferences = this . getHostPreferences ( ) ;
2258
- const changes = flattenCombineOutputResult (
2259
- combineProjectOutputFromEveryProject (
2260
- this . projectService ,
2261
- project => project . getLanguageService ( ) . getEditsForFileRename ( oldPath , newPath , formatOptions , preferences ) ,
2262
- ( a , b ) => a . fileName === b . fileName
2263
- )
2264
- ) ;
2265
- return simplifiedResult ? changes . map ( c => this . mapTextChangeToCodeEdit ( c ) ) : changes ;
2260
+
2261
+
2262
+ const seenFiles = new Set < string > ( ) ;
2263
+ const textChanges : FileTextChanges [ ] = [ ] ;
2264
+ // TODO (https://github.com/microsoft/TypeScript/issues/47839)
2265
+ // This appears to have been intended to search all projects but, in practice, it seems to only search
2266
+ // those that are downstream from already-loaded projects.
2267
+ this . projectService . loadAncestorProjectTree ( ) ;
2268
+ this . projectService . forEachEnabledProject ( project => {
2269
+ const projectTextChanges = project . getLanguageService ( ) . getEditsForFileRename ( oldPath , newPath , formatOptions , preferences ) ;
2270
+ const projectFiles : string [ ] = [ ] ;
2271
+ for ( const textChange of projectTextChanges ) {
2272
+ if ( ! seenFiles . has ( textChange . fileName ) ) {
2273
+ textChanges . push ( textChange ) ;
2274
+ projectFiles . push ( textChange . fileName ) ;
2275
+ }
2276
+ }
2277
+ for ( const file of projectFiles ) {
2278
+ seenFiles . add ( file ) ;
2279
+ }
2280
+ } ) ;
2281
+
2282
+ return simplifiedResult ? textChanges . map ( c => this . mapTextChangeToCodeEdit ( c ) ) : textChanges ;
2266
2283
}
2267
2284
2268
2285
private getCodeFixes ( args : protocol . CodeFixRequestArgs , simplifiedResult : boolean ) : readonly protocol . CodeFixAction [ ] | readonly CodeFixAction [ ] | undefined {
0 commit comments