@@ -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 {
@@ -1261,7 +1321,7 @@ namespace ts.server {
1261
1321
}
1262
1322
}
1263
1323
1264
- private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : OpenScriptInfoOrClosedFileInfo ) {
1324
+ private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : OpenScriptInfoOrClosedOrConfigFileInfo ) {
1265
1325
let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1266
1326
if ( configFileExistenceInfo ) {
1267
1327
// By default the info would get impacted by presence of config file since its in the detection path
@@ -1492,7 +1552,7 @@ namespace ts.server {
1492
1552
* The server must start searching from the directory containing
1493
1553
* the newly opened file.
1494
1554
*/
1495
- private forEachConfigFileLocation ( info : OpenScriptInfoOrClosedFileInfo , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1555
+ private forEachConfigFileLocation ( info : OpenScriptInfoOrClosedOrConfigFileInfo , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1496
1556
if ( this . syntaxOnly ) {
1497
1557
return undefined ;
1498
1558
}
@@ -1505,25 +1565,24 @@ namespace ts.server {
1505
1565
1506
1566
// If projectRootPath doesn't contain info.path, then do normal search for config file
1507
1567
const anySearchPathOk = ! projectRootPath || ! isSearchPathInProjectRoot ( ) ;
1568
+ // For ancestor of config file always ignore its own directory since its going to result in itself
1569
+ let searchInDirectory = ! isAncestorConfigFileInfo ( info ) ;
1508
1570
do {
1509
- const canonicalSearchPath = normalizedPathToPath ( searchPath , this . currentDirectory , this . toCanonicalFileName ) ;
1510
- const tsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "tsconfig.json" ) ) ;
1511
- let result = action ( tsconfigFileName , combinePaths ( canonicalSearchPath , "tsconfig.json" ) ) ;
1512
- if ( result ) {
1513
- return tsconfigFileName ;
1514
- }
1571
+ if ( searchInDirectory ) {
1572
+ const canonicalSearchPath = normalizedPathToPath ( searchPath , this . currentDirectory , this . toCanonicalFileName ) ;
1573
+ const tsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "tsconfig.json" ) ) ;
1574
+ let result = action ( tsconfigFileName , combinePaths ( canonicalSearchPath , "tsconfig.json" ) ) ;
1575
+ if ( result ) return tsconfigFileName ;
1515
1576
1516
- const jsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "jsconfig.json" ) ) ;
1517
- result = action ( jsconfigFileName , combinePaths ( canonicalSearchPath , "jsconfig.json" ) ) ;
1518
- if ( result ) {
1519
- return jsconfigFileName ;
1577
+ const jsconfigFileName = asNormalizedPath ( combinePaths ( searchPath , "jsconfig.json" ) ) ;
1578
+ result = action ( jsconfigFileName , combinePaths ( canonicalSearchPath , "jsconfig.json" ) ) ;
1579
+ if ( result ) return jsconfigFileName ;
1520
1580
}
1521
1581
1522
1582
const parentPath = asNormalizedPath ( getDirectoryPath ( searchPath ) ) ;
1523
- if ( parentPath === searchPath ) {
1524
- break ;
1525
- }
1583
+ if ( parentPath === searchPath ) break ;
1526
1584
searchPath = parentPath ;
1585
+ searchInDirectory = true ;
1527
1586
} while ( anySearchPathOk || isSearchPathInProjectRoot ( ) ) ;
1528
1587
1529
1588
return undefined ;
@@ -1539,7 +1598,7 @@ namespace ts.server {
1539
1598
* If script info is passed in, it is asserted to be open script info
1540
1599
* otherwise just file name
1541
1600
*/
1542
- private getConfigFileNameForFile ( info : OpenScriptInfoOrClosedFileInfo ) {
1601
+ private getConfigFileNameForFile ( info : OpenScriptInfoOrClosedOrConfigFileInfo ) {
1543
1602
if ( isOpenScriptInfo ( info ) ) Debug . assert ( info . isScriptOpen ( ) ) ;
1544
1603
this . logger . info ( `Search path: ${ getDirectoryPath ( info . fileName ) } ` ) ;
1545
1604
const configFileName = this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) =>
@@ -2062,7 +2121,7 @@ namespace ts.server {
2062
2121
if ( project . languageServiceEnabled &&
2063
2122
! project . isOrphan ( ) &&
2064
2123
! project . getCompilerOptions ( ) . preserveSymlinks &&
2065
- ! contains ( info . containingProjects , project ) ) {
2124
+ ! info . isAttached ( project ) ) {
2066
2125
if ( ! projects ) {
2067
2126
projects = createMultiMap ( ) ;
2068
2127
projects . add ( toAddInfo . path , project ) ;
@@ -2662,7 +2721,7 @@ namespace ts.server {
2662
2721
if ( ! project ) {
2663
2722
project = this . createLoadAndUpdateConfiguredProject ( configFileName , `Creating possible configured project for ${ info . fileName } to open` ) ;
2664
2723
// Send the event only if the project got created as part of this open request and info is part of the project
2665
- if ( info . isOrphan ( ) ) {
2724
+ if ( ! project . containsScriptInfo ( info ) ) {
2666
2725
// Since the file isnt part of configured project, do not send config file info
2667
2726
configFileName = undefined ;
2668
2727
}
@@ -2676,6 +2735,8 @@ namespace ts.server {
2676
2735
updateProjectIfDirty ( project ) ;
2677
2736
}
2678
2737
defaultConfigProject = project ;
2738
+ // Create ancestor configured project
2739
+ this . createAncestorProjects ( info , defaultConfigProject ) ;
2679
2740
}
2680
2741
}
2681
2742
@@ -2698,6 +2759,74 @@ namespace ts.server {
2698
2759
return { configFileName, configFileErrors, defaultConfigProject } ;
2699
2760
}
2700
2761
2762
+ private createAncestorProjects ( info : ScriptInfo , project : ConfiguredProject ) {
2763
+ // Skip if info is not part of default configured project
2764
+ if ( ! info . isAttached ( project ) ) return ;
2765
+
2766
+ // Create configured project till project root
2767
+ while ( true ) {
2768
+ // Skip if project is not composite
2769
+ if ( ! project . isInitialLoadPending ( ) && ! project . getCompilerOptions ( ) . composite ) return ;
2770
+
2771
+ // Get config file name
2772
+ const configFileName = this . getConfigFileNameForFile ( {
2773
+ fileName : project . getConfigFilePath ( ) ,
2774
+ path : info . path ,
2775
+ configFileInfo : true
2776
+ } ) ;
2777
+ if ( ! configFileName ) return ;
2778
+
2779
+ // find or delay load the project
2780
+ const ancestor = this . findConfiguredProjectByProjectName ( configFileName ) ||
2781
+ this . createConfiguredProjectWithDelayLoad ( configFileName , `Creating project possibly referencing default composite project ${ project . getProjectName ( ) } of open file ${ info . fileName } ` ) ;
2782
+ if ( ancestor . isInitialLoadPending ( ) ) {
2783
+ // Set a potential project reference
2784
+ ancestor . setPotentialProjectReference ( project . canonicalConfigFilePath ) ;
2785
+ }
2786
+ project = ancestor ;
2787
+ }
2788
+ }
2789
+
2790
+ /*@internal */
2791
+ loadAncestorProjectTree ( forProjects ?: ReadonlyMap < true > ) {
2792
+ forProjects = forProjects || mapDefinedMap (
2793
+ this . configuredProjects ,
2794
+ project => ! project . isInitialLoadPending ( ) || undefined
2795
+ ) ;
2796
+
2797
+ const seenProjects = createMap < true > ( ) ;
2798
+ // Work on array copy as we could add more projects as part of callback
2799
+ for ( const project of arrayFrom ( this . configuredProjects . values ( ) ) ) {
2800
+ // If this project has potential project reference for any of the project we are loading ancestor tree for
2801
+ // we need to load this project tree
2802
+ if ( forEachPotentialProjectReference (
2803
+ project ,
2804
+ potentialRefPath => forProjects ! . has ( potentialRefPath )
2805
+ ) ) {
2806
+ // Load children
2807
+ this . ensureProjectChildren ( project , seenProjects ) ;
2808
+ }
2809
+ }
2810
+ }
2811
+
2812
+ private ensureProjectChildren ( project : ConfiguredProject , seenProjects : Map < true > ) {
2813
+ if ( ! addToSeen ( seenProjects , project . canonicalConfigFilePath ) ) return ;
2814
+ // Update the project
2815
+ updateProjectIfDirty ( project ) ;
2816
+
2817
+ // Create tree because project is uptodate we only care of resolved references
2818
+ forEachResolvedProjectReference (
2819
+ project ,
2820
+ ref => {
2821
+ if ( ! ref ) return ;
2822
+ const configFileName = toNormalizedPath ( ref . sourceFile . fileName ) ;
2823
+ const child = this . findConfiguredProjectByProjectName ( configFileName ) ||
2824
+ this . createAndLoadConfiguredProject ( configFileName , `Creating project for reference of project: ${ project . projectName } ` ) ;
2825
+ this . ensureProjectChildren ( child , seenProjects ) ;
2826
+ }
2827
+ ) ;
2828
+ }
2829
+
2701
2830
private cleanupAfterOpeningFile ( toRetainConfigProjects : ConfiguredProject [ ] | ConfiguredProject | undefined ) {
2702
2831
// This was postponed from closeOpenFile to after opening next file,
2703
2832
// so that we can reuse the project if we need to right away
@@ -2729,6 +2858,16 @@ namespace ts.server {
2729
2858
2730
2859
private removeOrphanConfiguredProjects ( toRetainConfiguredProjects : ConfiguredProject [ ] | ConfiguredProject | undefined ) {
2731
2860
const toRemoveConfiguredProjects = cloneMap ( this . configuredProjects ) ;
2861
+ const markOriginalProjectsAsUsed = ( project : Project ) => {
2862
+ if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) {
2863
+ project . originalConfiguredProjects . forEach (
2864
+ ( _value , configuredProjectPath ) => {
2865
+ const project = this . getConfiguredProjectByCanonicalConfigFilePath ( configuredProjectPath ) ;
2866
+ return project && retainConfiguredProject ( project ) ;
2867
+ }
2868
+ ) ;
2869
+ }
2870
+ } ;
2732
2871
if ( toRetainConfiguredProjects ) {
2733
2872
if ( isArray ( toRetainConfiguredProjects ) ) {
2734
2873
toRetainConfiguredProjects . forEach ( retainConfiguredProject ) ;
@@ -2745,32 +2884,30 @@ namespace ts.server {
2745
2884
// 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
2746
2885
if ( project . hasOpenRef ( ) ) {
2747
2886
retainConfiguredProject ( project ) ;
2748
- markOriginalProjectsAsUsed ( project ) ;
2749
2887
}
2750
- else {
2888
+ else if ( toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ) {
2751
2889
// If the configured project for project reference has more than zero references, keep it alive
2752
- project . forEachResolvedProjectReference ( ref => {
2753
- if ( ref ) {
2754
- const refProject = this . configuredProjects . get ( ref . sourceFile . path ) ;
2755
- if ( refProject && refProject . hasOpenRef ( ) ) {
2756
- retainConfiguredProject ( project ) ;
2757
- }
2758
- }
2759
- } ) ;
2890
+ forEachReferencedProject (
2891
+ project ,
2892
+ ref => isRetained ( ref ) && retainConfiguredProject ( project )
2893
+ ) ;
2760
2894
}
2761
2895
} ) ;
2762
2896
2763
2897
// Remove all the non marked projects
2764
2898
toRemoveConfiguredProjects . forEach ( project => this . removeProject ( project ) ) ;
2765
2899
2766
- function markOriginalProjectsAsUsed ( project : Project ) {
2767
- if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) {
2768
- project . originalConfiguredProjects . forEach ( ( _value , configuredProjectPath ) => toRemoveConfiguredProjects . delete ( configuredProjectPath ) ) ;
2769
- }
2900
+ function isRetained ( project : ConfiguredProject ) {
2901
+ return project . hasOpenRef ( ) || ! toRemoveConfiguredProjects . has ( project . canonicalConfigFilePath ) ;
2770
2902
}
2771
2903
2772
2904
function retainConfiguredProject ( project : ConfiguredProject ) {
2773
- toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ;
2905
+ if ( toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ) {
2906
+ // Keep original projects used
2907
+ markOriginalProjectsAsUsed ( project ) ;
2908
+ // Keep all the references alive
2909
+ forEachReferencedProject ( project , retainConfiguredProject ) ;
2910
+ }
2774
2911
}
2775
2912
}
2776
2913
0 commit comments