@@ -383,10 +383,70 @@ namespace ts.server {
383
383
}
384
384
385
385
interface OriginalFileInfo { fileName : NormalizedPath ; path : Path ; }
386
+ interface AncestorConfigFileInfo {
387
+ /** config file name */ fileName : string ;
388
+ /** path of open file so we can look at correct root */ path : Path ;
389
+ configFileInfo : true ;
390
+ }
386
391
type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo ;
392
+ type OpenScriptInfoOrClosedOrConfigFileInfo = OpenScriptInfoOrClosedFileInfo | AncestorConfigFileInfo ;
393
+
394
+ function isOpenScriptInfo ( infoOrFileNameOrConfig : OpenScriptInfoOrClosedOrConfigFileInfo ) : infoOrFileNameOrConfig is ScriptInfo {
395
+ return ! ! ( infoOrFileNameOrConfig as ScriptInfo ) . containingProjects ;
396
+ }
397
+
398
+ function isAncestorConfigFileInfo ( infoOrFileNameOrConfig : OpenScriptInfoOrClosedOrConfigFileInfo ) : infoOrFileNameOrConfig is AncestorConfigFileInfo {
399
+ return ! ! ( infoOrFileNameOrConfig as AncestorConfigFileInfo ) . configFileInfo ;
400
+ }
401
+
402
+ function forEachResolvedProjectReference < T > (
403
+ project : ConfiguredProject ,
404
+ cb : ( resolvedProjectReference : ResolvedProjectReference | undefined , resolvedProjectReferencePath : Path ) => T | undefined
405
+ ) : T | undefined {
406
+ const program = project . getCurrentProgram ( ) ;
407
+ return program && program . forEachResolvedProjectReference ( cb ) ;
408
+ }
409
+
410
+ function forEachPotentialProjectReference < T > (
411
+ project : ConfiguredProject ,
412
+ cb : ( potentialProjectReference : Path ) => T | undefined
413
+ ) : T | undefined {
414
+ return project . potentialProjectReferences &&
415
+ forEachKey ( project . potentialProjectReferences , cb ) ;
416
+ }
417
+
418
+ function forEachAnyProjectReferenceKind < T > (
419
+ project : ConfiguredProject ,
420
+ cb : ( resolvedProjectReference : ResolvedProjectReference | undefined , resolvedProjectReferencePath : Path ) => T | undefined ,
421
+ cbProjectRef : ( projectReference : ProjectReference ) => T | undefined ,
422
+ cbPotentialProjectRef : ( potentialProjectReference : Path ) => T | undefined
423
+ ) : T | undefined {
424
+ return project . getCurrentProgram ( ) ?
425
+ forEachResolvedProjectReference ( project , cb ) :
426
+ project . isInitialLoadPending ( ) ?
427
+ forEachPotentialProjectReference ( project , cbPotentialProjectRef ) :
428
+ forEach ( project . getProjectReferences ( ) , cbProjectRef ) ;
429
+ }
430
+
431
+ function callbackRefProject < T > (
432
+ project : ConfiguredProject ,
433
+ cb : ( refProj : ConfiguredProject ) => T | undefined ,
434
+ refPath : Path | undefined
435
+ ) {
436
+ const refProject = refPath && project . projectService . configuredProjects . get ( refPath ) ;
437
+ return refProject && cb ( refProject ) ;
438
+ }
387
439
388
- function isOpenScriptInfo ( infoOrFileName : OpenScriptInfoOrClosedFileInfo ) : infoOrFileName is ScriptInfo {
389
- return ! ! ( infoOrFileName as ScriptInfo ) . containingProjects ;
440
+ function forEachReferencedProject < T > (
441
+ project : ConfiguredProject ,
442
+ cb : ( refProj : ConfiguredProject ) => T | undefined
443
+ ) : T | undefined {
444
+ return forEachAnyProjectReferenceKind (
445
+ project ,
446
+ resolvedRef => callbackRefProject ( project , cb , resolvedRef && resolvedRef . sourceFile . path ) ,
447
+ projectRef => callbackRefProject ( project , cb , project . toPath ( projectRef . path ) ) ,
448
+ potentialProjectRef => callbackRefProject ( project , cb , potentialProjectRef )
449
+ ) ;
390
450
}
391
451
392
452
interface ScriptInfoInNodeModulesWatcher extends FileWatcher {
@@ -1260,7 +1320,7 @@ namespace ts.server {
1260
1320
}
1261
1321
}
1262
1322
1263
- private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : OpenScriptInfoOrClosedFileInfo ) {
1323
+ private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : OpenScriptInfoOrClosedOrConfigFileInfo ) {
1264
1324
let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1265
1325
if ( configFileExistenceInfo ) {
1266
1326
// By default the info would get impacted by presence of config file since its in the detection path
@@ -1491,7 +1551,7 @@ namespace ts.server {
1491
1551
* The server must start searching from the directory containing
1492
1552
* the newly opened file.
1493
1553
*/
1494
- private forEachConfigFileLocation ( info : OpenScriptInfoOrClosedFileInfo , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1554
+ private forEachConfigFileLocation ( info : OpenScriptInfoOrClosedOrConfigFileInfo , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1495
1555
if ( this . syntaxOnly ) {
1496
1556
return undefined ;
1497
1557
}
@@ -1504,25 +1564,24 @@ namespace ts.server {
1504
1564
1505
1565
// If projectRootPath doesn't contain info.path, then do normal search for config file
1506
1566
const anySearchPathOk = ! projectRootPath || ! isSearchPathInProjectRoot ( ) ;
1567
+ // For ancestor of config file always ignore its own directory since its going to result in itself
1568
+ let searchInDirectory = ! isAncestorConfigFileInfo ( info ) ;
1507
1569
do {
1508
- const canonicalSearchPath = normalizedPathToPath ( searchPath , this . currentDirectory , this . toCanonicalFileName ) ;
1509
- const tsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "tsconfig.json" ) ) ;
1510
- let result = action ( tsconfigFileName , combinePaths ( canonicalSearchPath , "tsconfig.json" ) ) ;
1511
- if ( result ) {
1512
- return tsconfigFileName ;
1513
- }
1570
+ if ( searchInDirectory ) {
1571
+ const canonicalSearchPath = normalizedPathToPath ( searchPath , this . currentDirectory , this . toCanonicalFileName ) ;
1572
+ const tsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "tsconfig.json" ) ) ;
1573
+ let result = action ( tsconfigFileName , combinePaths ( canonicalSearchPath , "tsconfig.json" ) ) ;
1574
+ if ( result ) return tsconfigFileName ;
1514
1575
1515
- const jsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "jsconfig.json" ) ) ;
1516
- result = action ( jsconfigFileName , combinePaths ( canonicalSearchPath , "jsconfig.json" ) ) ;
1517
- if ( result ) {
1518
- return jsconfigFileName ;
1576
+ const jsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "jsconfig.json" ) ) ;
1577
+ result = action ( jsconfigFileName , combinePaths ( canonicalSearchPath , "jsconfig.json" ) ) ;
1578
+ if ( result ) return jsconfigFileName ;
1519
1579
}
1520
1580
1521
1581
const parentPath = asNormalizedPath ( getDirectoryPath ( searchPath ) ) ;
1522
- if ( parentPath === searchPath ) {
1523
- break ;
1524
- }
1582
+ if ( parentPath === searchPath ) break ;
1525
1583
searchPath = parentPath ;
1584
+ searchInDirectory = true ;
1526
1585
} while ( anySearchPathOk || isSearchPathInProjectRoot ( ) ) ;
1527
1586
1528
1587
return undefined ;
@@ -1538,7 +1597,7 @@ namespace ts.server {
1538
1597
* If script info is passed in, it is asserted to be open script info
1539
1598
* otherwise just file name
1540
1599
*/
1541
- private getConfigFileNameForFile ( info : OpenScriptInfoOrClosedFileInfo ) {
1600
+ private getConfigFileNameForFile ( info : OpenScriptInfoOrClosedOrConfigFileInfo ) {
1542
1601
if ( isOpenScriptInfo ( info ) ) Debug . assert ( info . isScriptOpen ( ) ) ;
1543
1602
this . logger . info ( `Search path: ${ getDirectoryPath ( info . fileName ) } ` ) ;
1544
1603
const configFileName = this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) =>
@@ -2061,7 +2120,7 @@ namespace ts.server {
2061
2120
if ( project . languageServiceEnabled &&
2062
2121
! project . isOrphan ( ) &&
2063
2122
! project . getCompilerOptions ( ) . preserveSymlinks &&
2064
- ! contains ( info . containingProjects , project ) ) {
2123
+ ! info . isAttached ( project ) ) {
2065
2124
if ( ! projects ) {
2066
2125
projects = createMultiMap ( ) ;
2067
2126
projects . add ( toAddInfo . path , project ) ;
@@ -2661,7 +2720,7 @@ namespace ts.server {
2661
2720
if ( ! project ) {
2662
2721
project = this . createLoadAndUpdateConfiguredProject ( configFileName , `Creating possible configured project for ${ info . fileName } to open` ) ;
2663
2722
// Send the event only if the project got created as part of this open request and info is part of the project
2664
- if ( info . isOrphan ( ) ) {
2723
+ if ( ! project . containsScriptInfo ( info ) ) {
2665
2724
// Since the file isnt part of configured project, do not send config file info
2666
2725
configFileName = undefined ;
2667
2726
}
@@ -2675,6 +2734,8 @@ namespace ts.server {
2675
2734
updateProjectIfDirty ( project ) ;
2676
2735
}
2677
2736
defaultConfigProject = project ;
2737
+ // Create ancestor configured project
2738
+ this . createAncestorProjects ( info , defaultConfigProject ) ;
2678
2739
}
2679
2740
}
2680
2741
@@ -2697,6 +2758,74 @@ namespace ts.server {
2697
2758
return { configFileName, configFileErrors, defaultConfigProject } ;
2698
2759
}
2699
2760
2761
+ private createAncestorProjects ( info : ScriptInfo , project : ConfiguredProject ) {
2762
+ // Skip if info is not part of default configured project
2763
+ if ( ! info . isAttached ( project ) ) return ;
2764
+
2765
+ // Create configured project till project root
2766
+ while ( true ) {
2767
+ // Skip if project is not composite
2768
+ if ( ! project . isInitialLoadPending ( ) && ! project . getCompilerOptions ( ) . composite ) return ;
2769
+
2770
+ // Get config file name
2771
+ const configFileName = this . getConfigFileNameForFile ( {
2772
+ fileName : project . getConfigFilePath ( ) ,
2773
+ path : info . path ,
2774
+ configFileInfo : true
2775
+ } ) ;
2776
+ if ( ! configFileName ) return ;
2777
+
2778
+ // find or delay load the project
2779
+ const ancestor = this . findConfiguredProjectByProjectName ( configFileName ) ||
2780
+ this . createConfiguredProjectWithDelayLoad ( configFileName , `Creating project possibly referencing default composite project ${ project . getProjectName ( ) } of open file ${ info . fileName } ` ) ;
2781
+ if ( ancestor . isInitialLoadPending ( ) ) {
2782
+ // Set a potential project reference
2783
+ ancestor . setPotentialProjectReference ( project . canonicalConfigFilePath ) ;
2784
+ }
2785
+ project = ancestor ;
2786
+ }
2787
+ }
2788
+
2789
+ /*@internal */
2790
+ loadAncestorProjectTree ( forProjects ?: ReadonlyMap < true > ) {
2791
+ forProjects = forProjects || mapDefinedMap (
2792
+ this . configuredProjects ,
2793
+ project => ! project . isInitialLoadPending ( ) || undefined
2794
+ ) ;
2795
+
2796
+ const seenProjects = createMap < true > ( ) ;
2797
+ // Work on array copy as we could add more projects as part of callback
2798
+ for ( const project of arrayFrom ( this . configuredProjects . values ( ) ) ) {
2799
+ // If this project has potential project reference for any of the project we are loading ancestor tree for
2800
+ // we need to load this project tree
2801
+ if ( forEachPotentialProjectReference (
2802
+ project ,
2803
+ potentialRefPath => forProjects ! . has ( potentialRefPath )
2804
+ ) ) {
2805
+ // Load children
2806
+ this . ensureProjectChildren ( project , seenProjects ) ;
2807
+ }
2808
+ }
2809
+ }
2810
+
2811
+ private ensureProjectChildren ( project : ConfiguredProject , seenProjects : Map < true > ) {
2812
+ if ( ! addToSeen ( seenProjects , project . canonicalConfigFilePath ) ) return ;
2813
+ // Update the project
2814
+ updateProjectIfDirty ( project ) ;
2815
+
2816
+ // Create tree because project is uptodate we only care of resolved references
2817
+ forEachResolvedProjectReference (
2818
+ project ,
2819
+ ref => {
2820
+ if ( ! ref ) return ;
2821
+ const configFileName = toNormalizedPath ( ref . sourceFile . fileName ) ;
2822
+ const child = this . findConfiguredProjectByProjectName ( configFileName ) ||
2823
+ this . createAndLoadConfiguredProject ( configFileName , `Creating project for reference of project: ${ project . projectName } ` ) ;
2824
+ this . ensureProjectChildren ( child , seenProjects ) ;
2825
+ }
2826
+ ) ;
2827
+ }
2828
+
2700
2829
private cleanupAfterOpeningFile ( toRetainConfigProjects : ConfiguredProject [ ] | ConfiguredProject | undefined ) {
2701
2830
// This was postponed from closeOpenFile to after opening next file,
2702
2831
// so that we can reuse the project if we need to right away
@@ -2728,6 +2857,16 @@ namespace ts.server {
2728
2857
2729
2858
private removeOrphanConfiguredProjects ( toRetainConfiguredProjects : ConfiguredProject [ ] | ConfiguredProject | undefined ) {
2730
2859
const toRemoveConfiguredProjects = cloneMap ( this . configuredProjects ) ;
2860
+ const markOriginalProjectsAsUsed = ( project : Project ) => {
2861
+ if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) {
2862
+ project . originalConfiguredProjects . forEach (
2863
+ ( _value , configuredProjectPath ) => {
2864
+ const project = this . getConfiguredProjectByCanonicalConfigFilePath ( configuredProjectPath ) ;
2865
+ return project && retainConfiguredProject ( project ) ;
2866
+ }
2867
+ ) ;
2868
+ }
2869
+ } ;
2731
2870
if ( toRetainConfiguredProjects ) {
2732
2871
if ( isArray ( toRetainConfiguredProjects ) ) {
2733
2872
toRetainConfiguredProjects . forEach ( retainConfiguredProject ) ;
@@ -2744,32 +2883,30 @@ namespace ts.server {
2744
2883
// If project has open ref (there are more than zero references from external project/open file), keep it alive as well as any project it references
2745
2884
if ( project . hasOpenRef ( ) ) {
2746
2885
retainConfiguredProject ( project ) ;
2747
- markOriginalProjectsAsUsed ( project ) ;
2748
2886
}
2749
- else {
2887
+ else if ( toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ) {
2750
2888
// If the configured project for project reference has more than zero references, keep it alive
2751
- project . forEachResolvedProjectReference ( ref => {
2752
- if ( ref ) {
2753
- const refProject = this . configuredProjects . get ( ref . sourceFile . path ) ;
2754
- if ( refProject && refProject . hasOpenRef ( ) ) {
2755
- retainConfiguredProject ( project ) ;
2756
- }
2757
- }
2758
- } ) ;
2889
+ forEachReferencedProject (
2890
+ project ,
2891
+ ref => isRetained ( ref ) && retainConfiguredProject ( project )
2892
+ ) ;
2759
2893
}
2760
2894
} ) ;
2761
2895
2762
2896
// Remove all the non marked projects
2763
2897
toRemoveConfiguredProjects . forEach ( project => this . removeProject ( project ) ) ;
2764
2898
2765
- function markOriginalProjectsAsUsed ( project : Project ) {
2766
- if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) {
2767
- project . originalConfiguredProjects . forEach ( ( _value , configuredProjectPath ) => toRemoveConfiguredProjects . delete ( configuredProjectPath ) ) ;
2768
- }
2899
+ function isRetained ( project : ConfiguredProject ) {
2900
+ return project . hasOpenRef ( ) || ! toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ;
2769
2901
}
2770
2902
2771
2903
function retainConfiguredProject ( project : ConfiguredProject ) {
2772
- toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ;
2904
+ if ( toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ) {
2905
+ // Keep original projects used
2906
+ markOriginalProjectsAsUsed ( project ) ;
2907
+ // Keep all the references alive
2908
+ forEachReferencedProject ( project , retainConfiguredProject ) ;
2909
+ }
2773
2910
}
2774
2911
}
2775
2912
0 commit comments