@@ -326,6 +326,13 @@ namespace ts.server {
326
326
syntaxOnly ?: boolean ;
327
327
}
328
328
329
+ interface OriginalFileInfo { fileName : NormalizedPath ; path : Path ; }
330
+ type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo ;
331
+
332
+ function isOpenScriptInfo ( infoOrFileName : OpenScriptInfoOrClosedFileInfo ) : infoOrFileName is ScriptInfo {
333
+ return ! ! ( infoOrFileName as ScriptInfo ) . containingProjects ;
334
+ }
335
+
329
336
function getDetailWatchInfo ( watchType : WatchType , project : Project | undefined ) {
330
337
return `Project: ${ project ? project . getProjectName ( ) : "" } WatchType: ${ watchType } ` ;
331
338
}
@@ -700,7 +707,7 @@ namespace ts.server {
700
707
701
708
/* @internal */
702
709
private forEachProject ( cb : ( project : Project ) => void ) {
703
- for ( const p of this . inferredProjects ) cb ( p ) ;
710
+ this . inferredProjects . forEach ( cb ) ;
704
711
this . configuredProjects . forEach ( cb ) ;
705
712
this . externalProjects . forEach ( cb ) ;
706
713
}
@@ -1044,12 +1051,12 @@ namespace ts.server {
1044
1051
}
1045
1052
}
1046
1053
1047
- private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : ScriptInfo ) {
1054
+ private configFileExists ( configFileName : NormalizedPath , canonicalConfigFilePath : string , info : OpenScriptInfoOrClosedFileInfo ) {
1048
1055
let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1049
1056
if ( configFileExistenceInfo ) {
1050
1057
// By default the info would get impacted by presence of config file since its in the detection path
1051
1058
// Only adding the info as a root to inferred project will need the existence to be watched by file watcher
1052
- if ( ! configFileExistenceInfo . openFilesImpactedByConfigFile . has ( info . path ) ) {
1059
+ if ( isOpenScriptInfo ( info ) && ! configFileExistenceInfo . openFilesImpactedByConfigFile . has ( info . path ) ) {
1053
1060
configFileExistenceInfo . openFilesImpactedByConfigFile . set ( info . path , false ) ;
1054
1061
this . logConfigFileWatchUpdate ( configFileName , canonicalConfigFilePath , configFileExistenceInfo , ConfigFileWatcherStatus . OpenFilesImpactedByConfigFileAdd ) ;
1055
1062
}
@@ -1066,9 +1073,11 @@ namespace ts.server {
1066
1073
// Or the whole chain of config files for the roots of the inferred projects
1067
1074
1068
1075
// Cache the host value of file exists and add the info to map of open files impacted by this config file
1069
- const openFilesImpactedByConfigFile = createMap < boolean > ( ) ;
1070
- openFilesImpactedByConfigFile . set ( info . path , false ) ;
1071
1076
const exists = this . host . fileExists ( configFileName ) ;
1077
+ const openFilesImpactedByConfigFile = createMap < boolean > ( ) ;
1078
+ if ( isOpenScriptInfo ( info ) ) {
1079
+ openFilesImpactedByConfigFile . set ( info . path , false ) ;
1080
+ }
1072
1081
configFileExistenceInfo = { exists, openFilesImpactedByConfigFile } ;
1073
1082
this . configFileExistenceInfoCache . set ( canonicalConfigFilePath , configFileExistenceInfo ) ;
1074
1083
this . logConfigFileWatchUpdate ( configFileName , canonicalConfigFilePath , configFileExistenceInfo , ConfigFileWatcherStatus . OpenFilesImpactedByConfigFileAdd ) ;
@@ -1180,7 +1189,7 @@ namespace ts.server {
1180
1189
*/
1181
1190
private stopWatchingConfigFilesForClosedScriptInfo ( info : ScriptInfo ) {
1182
1191
Debug . assert ( ! info . isScriptOpen ( ) ) ;
1183
- this . forEachConfigFileLocation ( info , /*infoShouldBeOpen*/ true , ( configFileName , canonicalConfigFilePath ) => {
1192
+ this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) => {
1184
1193
const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1185
1194
if ( configFileExistenceInfo ) {
1186
1195
const infoIsRootOfInferredProject = configFileExistenceInfo . openFilesImpactedByConfigFile . get ( info . path ) ;
@@ -1214,7 +1223,7 @@ namespace ts.server {
1214
1223
/* @internal */
1215
1224
startWatchingConfigFilesForInferredProjectRoot ( info : ScriptInfo ) {
1216
1225
Debug . assert ( info . isScriptOpen ( ) ) ;
1217
- this . forEachConfigFileLocation ( info , /*infoShouldBeOpen*/ true , ( configFileName , canonicalConfigFilePath ) => {
1226
+ this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) => {
1218
1227
let configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1219
1228
if ( ! configFileExistenceInfo ) {
1220
1229
// Create the cache
@@ -1242,7 +1251,7 @@ namespace ts.server {
1242
1251
*/
1243
1252
/* @internal */
1244
1253
stopWatchingConfigFilesForInferredProjectRoot ( info : ScriptInfo ) {
1245
- this . forEachConfigFileLocation ( info , /*infoShouldBeOpen*/ true , ( configFileName , canonicalConfigFilePath ) => {
1254
+ this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) => {
1246
1255
const configFileExistenceInfo = this . configFileExistenceInfoCache . get ( canonicalConfigFilePath ) ;
1247
1256
if ( configFileExistenceInfo && configFileExistenceInfo . openFilesImpactedByConfigFile . has ( info . path ) ) {
1248
1257
Debug . assert ( info . isScriptOpen ( ) ) ;
@@ -1265,12 +1274,12 @@ namespace ts.server {
1265
1274
* The server must start searching from the directory containing
1266
1275
* the newly opened file.
1267
1276
*/
1268
- private forEachConfigFileLocation ( info : ScriptInfo , infoShouldBeOpen : boolean , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1277
+ private forEachConfigFileLocation ( info : OpenScriptInfoOrClosedFileInfo , action : ( configFileName : NormalizedPath , canonicalConfigFilePath : string ) => boolean | void ) {
1269
1278
if ( this . syntaxOnly ) {
1270
1279
return undefined ;
1271
1280
}
1272
1281
1273
- Debug . assert ( ! infoShouldBeOpen || this . openFiles . has ( info . path ) ) ;
1282
+ Debug . assert ( ! isOpenScriptInfo ( info ) || this . openFiles . has ( info . path ) ) ;
1274
1283
const projectRootPath = this . openFiles . get ( info . path ) ;
1275
1284
1276
1285
let searchPath = asNormalizedPath ( getDirectoryPath ( info . fileName ) ) ;
@@ -1309,11 +1318,13 @@ namespace ts.server {
1309
1318
* current directory (the directory in which tsc was invoked).
1310
1319
* The server must start searching from the directory containing
1311
1320
* the newly opened file.
1321
+ * If script info is passed in, it is asserted to be open script info
1322
+ * otherwise just file name
1312
1323
*/
1313
- private getConfigFileNameForFile ( info : ScriptInfo , infoShouldBeOpen : boolean ) {
1314
- if ( infoShouldBeOpen ) Debug . assert ( info . isScriptOpen ( ) ) ;
1324
+ private getConfigFileNameForFile ( info : OpenScriptInfoOrClosedFileInfo ) {
1325
+ if ( isOpenScriptInfo ( info ) ) Debug . assert ( info . isScriptOpen ( ) ) ;
1315
1326
this . logger . info ( `Search path: ${ getDirectoryPath ( info . fileName ) } ` ) ;
1316
- const configFileName = this . forEachConfigFileLocation ( info , infoShouldBeOpen , ( configFileName , canonicalConfigFilePath ) =>
1327
+ const configFileName = this . forEachConfigFileLocation ( info , ( configFileName , canonicalConfigFilePath ) =>
1317
1328
this . configFileExists ( configFileName , canonicalConfigFilePath , info ) ) ;
1318
1329
if ( configFileName ) {
1319
1330
this . logger . info ( `For info: ${ info . fileName } :: Config file name: ${ configFileName } ` ) ;
@@ -1683,10 +1694,12 @@ namespace ts.server {
1683
1694
if ( ! this . eventHandler || this . suppressDiagnosticEvents ) {
1684
1695
return ;
1685
1696
}
1697
+ const diagnostics = project . getLanguageService ( ) . getCompilerOptionsDiagnostics ( ) ;
1698
+ diagnostics . push ( ...project . getAllProjectErrors ( ) ) ;
1686
1699
1687
1700
this . eventHandler ( < ConfigFileDiagEvent > {
1688
1701
eventName : ConfigFileDiagEvent ,
1689
- data : { configFileName : project . getConfigFilePath ( ) , diagnostics : project . getAllProjectErrors ( ) , triggerFile }
1702
+ data : { configFileName : project . getConfigFilePath ( ) , diagnostics, triggerFile }
1690
1703
} ) ;
1691
1704
}
1692
1705
@@ -2003,7 +2016,7 @@ namespace ts.server {
2003
2016
// we first detect if there is already a configured project created for it: if so,
2004
2017
// we re- read the tsconfig file content and update the project only if we havent already done so
2005
2018
// otherwise we create a new one.
2006
- const configFileName = this . getConfigFileNameForFile ( info , /*infoShouldBeOpen*/ true ) ;
2019
+ const configFileName = this . getConfigFileNameForFile ( info ) ;
2007
2020
if ( configFileName ) {
2008
2021
const project = this . findConfiguredProjectByProjectName ( configFileName ) ;
2009
2022
if ( ! project ) {
@@ -2091,17 +2104,40 @@ namespace ts.server {
2091
2104
return this . openClientFileWithNormalizedPath ( toNormalizedPath ( fileName ) , fileContent , scriptKind , /*hasMixedContent*/ false , projectRootPath ? toNormalizedPath ( projectRootPath ) : undefined ) ;
2092
2105
}
2093
2106
2094
- /** @internal */
2095
- getProjectForFileWithoutOpening ( fileName : NormalizedPath ) : { readonly scriptInfo : ScriptInfo , readonly projects : ReadonlyArray < Project > } | undefined {
2096
- const scriptInfo = this . filenameToScriptInfo . get ( fileName ) ||
2097
- this . getOrCreateScriptInfoNotOpenedByClientForNormalizedPath ( fileName , this . currentDirectory , /*fileContent*/ undefined , /*scriptKind*/ undefined , /*hasMixedContent*/ undefined ) ;
2098
- if ( ! scriptInfo ) return undefined ;
2099
- if ( scriptInfo . containingProjects . length ) {
2100
- return { scriptInfo, projects : scriptInfo . containingProjects } ;
2107
+ /*@internal */
2108
+ getOriginalLocationEnsuringConfiguredProject ( project : Project , location : sourcemaps . SourceMappableLocation ) : sourcemaps . SourceMappableLocation | undefined {
2109
+ const originalLocation = project . getSourceMapper ( ) . tryGetOriginalLocation ( location ) ;
2110
+ if ( ! originalLocation ) return undefined ;
2111
+
2112
+ const { fileName } = originalLocation ;
2113
+ if ( ! this . getScriptInfo ( fileName ) && ! this . host . fileExists ( fileName ) ) return undefined ;
2114
+
2115
+ const originalFileInfo : OriginalFileInfo = { fileName : toNormalizedPath ( fileName ) , path : this . toPath ( fileName ) } ;
2116
+ const configFileName = this . getConfigFileNameForFile ( originalFileInfo ) ;
2117
+ if ( ! configFileName ) return undefined ;
2118
+
2119
+ const configuredProject = this . findConfiguredProjectByProjectName ( configFileName ) || this . createConfiguredProject ( configFileName ) ;
2120
+ updateProjectIfDirty ( configuredProject ) ;
2121
+ // Keep this configured project as referenced from project
2122
+ addOriginalConfiguredProject ( configuredProject ) ;
2123
+
2124
+ const originalScriptInfo = this . getScriptInfo ( fileName ) ;
2125
+ if ( ! originalScriptInfo || ! originalScriptInfo . containingProjects . length ) return undefined ;
2126
+
2127
+ // Add configured projects as referenced
2128
+ originalScriptInfo . containingProjects . forEach ( project => {
2129
+ if ( project . projectKind === ProjectKind . Configured ) {
2130
+ addOriginalConfiguredProject ( project as ConfiguredProject ) ;
2131
+ }
2132
+ } ) ;
2133
+ return originalLocation ;
2134
+
2135
+ function addOriginalConfiguredProject ( originalProject : ConfiguredProject ) {
2136
+ if ( ! project . originalConfiguredProjects ) {
2137
+ project . originalConfiguredProjects = createMap < true > ( ) ;
2138
+ }
2139
+ project . originalConfiguredProjects . set ( originalProject . canonicalConfigFilePath , true ) ;
2101
2140
}
2102
- const configFileName = this . getConfigFileNameForFile ( scriptInfo , /*infoShouldBeOpen*/ false ) ;
2103
- const project = configFileName === undefined ? undefined : this . findConfiguredProjectByProjectName ( configFileName ) || this . createConfiguredProject ( configFileName ) ;
2104
- return project && project . containsScriptInfo ( scriptInfo ) ? { scriptInfo, projects : [ project ] } : undefined ;
2105
2141
}
2106
2142
2107
2143
/** @internal */
@@ -2126,7 +2162,7 @@ namespace ts.server {
2126
2162
this . openFiles . set ( info . path , projectRootPath ) ;
2127
2163
let project : ConfiguredProject | ExternalProject | undefined = this . findExternalProjectContainingOpenScriptInfo ( info ) ;
2128
2164
if ( ! project && ! this . syntaxOnly ) { // Checking syntaxOnly is an optimization
2129
- configFileName = this . getConfigFileNameForFile ( info , /*infoShouldBeOpen*/ true ) ;
2165
+ configFileName = this . getConfigFileNameForFile ( info ) ;
2130
2166
if ( configFileName ) {
2131
2167
project = this . findConfiguredProjectByProjectName ( configFileName ) ;
2132
2168
if ( ! project ) {
@@ -2164,14 +2200,9 @@ namespace ts.server {
2164
2200
}
2165
2201
Debug . assert ( ! info . isOrphan ( ) ) ;
2166
2202
2167
- // Remove the configured projects that have zero references from open files.
2168
2203
// This was postponed from closeOpenFile to after opening next file,
2169
2204
// so that we can reuse the project if we need to right away
2170
- this . configuredProjects . forEach ( project => {
2171
- if ( ! project . hasOpenRef ( ) ) {
2172
- this . removeProject ( project ) ;
2173
- }
2174
- } ) ;
2205
+ this . removeOrphanConfiguredProjects ( ) ;
2175
2206
2176
2207
// Remove orphan inferred projects now that we have reused projects
2177
2208
// We need to create a duplicate because we cant guarantee order after removal
@@ -2199,6 +2230,30 @@ namespace ts.server {
2199
2230
return { configFileName, configFileErrors } ;
2200
2231
}
2201
2232
2233
+ private removeOrphanConfiguredProjects ( ) {
2234
+ const toRemoveConfiguredProjects = cloneMap ( this . configuredProjects ) ;
2235
+
2236
+ // Do not remove configured projects that are used as original projects of other
2237
+ this . inferredProjects . forEach ( markOriginalProjectsAsUsed ) ;
2238
+ this . externalProjects . forEach ( markOriginalProjectsAsUsed ) ;
2239
+ this . configuredProjects . forEach ( project => {
2240
+ // 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
2241
+ if ( project . hasOpenRef ( ) ) {
2242
+ toRemoveConfiguredProjects . delete ( project . canonicalConfigFilePath ) ;
2243
+ markOriginalProjectsAsUsed ( project ) ;
2244
+ }
2245
+ } ) ;
2246
+
2247
+ // Remove all the non marked projects
2248
+ toRemoveConfiguredProjects . forEach ( project => this . removeProject ( project ) ) ;
2249
+
2250
+ function markOriginalProjectsAsUsed ( project : Project ) {
2251
+ if ( ! project . isOrphan ( ) && project . originalConfiguredProjects ) {
2252
+ project . originalConfiguredProjects . forEach ( ( _value , configuredProjectPath ) => toRemoveConfiguredProjects . delete ( configuredProjectPath ) ) ;
2253
+ }
2254
+ }
2255
+ }
2256
+
2202
2257
private telemetryOnOpenFile ( scriptInfo : ScriptInfo ) : void {
2203
2258
if ( this . syntaxOnly || ! this . eventHandler || ! scriptInfo . isJavaScript ( ) || ! addToSeen ( this . allJsFilesForOpenFileTelemetry , scriptInfo . path ) ) {
2204
2259
return ;
0 commit comments