Skip to content

Commit 9d2f618

Browse files
committed
Create ancestor projects when opening file
1 parent 62419bb commit 9d2f618

File tree

2 files changed

+97
-25
lines changed

2 files changed

+97
-25
lines changed

src/server/editorServices.ts

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ namespace ts.server {
385385
syntaxOnly?: boolean;
386386
}
387387

388-
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; }
388+
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; openInfoPathForConfigFile?: Path; }
389389
type OpenScriptInfoOrClosedFileInfo = ScriptInfo | OriginalFileInfo;
390390

391391
function isOpenScriptInfo(infoOrFileName: OpenScriptInfoOrClosedFileInfo): infoOrFileName is ScriptInfo {
@@ -1009,6 +1009,10 @@ namespace ts.server {
10091009
}
10101010
else {
10111011
this.logConfigFileWatchUpdate(project.getConfigFilePath(), project.canonicalConfigFilePath, configFileExistenceInfo, ConfigFileWatcherStatus.ReloadingInferredRootFiles);
1012+
if (project.isInitialLoadPending()) {
1013+
return;
1014+
}
1015+
10121016
project.pendingReload = ConfigFileProgramReloadLevel.Full;
10131017
project.pendingReloadReason = "Change in config file detected";
10141018
this.delayUpdateProjectGraph(project);
@@ -1416,32 +1420,38 @@ namespace ts.server {
14161420
}
14171421

14181422
Debug.assert(!isOpenScriptInfo(info) || this.openFiles.has(info.path));
1419-
const projectRootPath = this.openFiles.get(info.path);
1423+
const openInfoPathForConfigFile = !isOpenScriptInfo(info) ? info.openInfoPathForConfigFile : undefined;
1424+
const projectRootPath = this.openFiles.get(openInfoPathForConfigFile || info.path);
14201425

14211426
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
14221427
const isSearchPathInProjectRoot = () => containsPath(projectRootPath!, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames);
14231428

14241429
// If projectRootPath doesn't contain info.path, then do normal search for config file
14251430
const anySearchPathOk = !projectRootPath || !isSearchPathInProjectRoot();
1431+
// For config files always ignore its directory since that would just result to same config file
1432+
let ignoreDirectory = !!openInfoPathForConfigFile;
14261433
do {
1427-
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
1428-
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
1429-
let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json"));
1430-
if (result) {
1431-
return tsconfigFileName;
1432-
}
1434+
if (!ignoreDirectory) {
1435+
const canonicalSearchPath = normalizedPathToPath(searchPath, this.currentDirectory, this.toCanonicalFileName);
1436+
const tsconfigFileName = asNormalizedPath(combinePaths(searchPath, "tsconfig.json"));
1437+
let result = action(tsconfigFileName, combinePaths(canonicalSearchPath, "tsconfig.json"));
1438+
if (result) {
1439+
return tsconfigFileName;
1440+
}
14331441

1434-
const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json"));
1435-
result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json"));
1436-
if (result) {
1437-
return jsconfigFileName;
1442+
const jsconfigFileName = asNormalizedPath(combinePaths(searchPath, "jsconfig.json"));
1443+
result = action(jsconfigFileName, combinePaths(canonicalSearchPath, "jsconfig.json"));
1444+
if (result) {
1445+
return jsconfigFileName;
1446+
}
14381447
}
14391448

14401449
const parentPath = asNormalizedPath(getDirectoryPath(searchPath));
14411450
if (parentPath === searchPath) {
14421451
break;
14431452
}
14441453
searchPath = parentPath;
1454+
ignoreDirectory = false;
14451455
} while (anySearchPathOk || isSearchPathInProjectRoot());
14461456

14471457
return undefined;
@@ -2431,7 +2441,7 @@ namespace ts.server {
24312441
if (!project) {
24322442
project = this.createLoadAndUpdateConfiguredProject(configFileName, `Creating possible configured project for ${fileName} to open`);
24332443
// Send the event only if the project got created as part of this open request and info is part of the project
2434-
if (info.isOrphan()) {
2444+
if (!project.containsScriptInfo(info)) {
24352445
// Since the file isnt part of configured project, do not send config file info
24362446
configFileName = undefined;
24372447
}
@@ -2444,6 +2454,8 @@ namespace ts.server {
24442454
// Ensure project is ready to check if it contains opened script info
24452455
updateProjectIfDirty(project);
24462456
}
2457+
// Traverse till project Root and create those configured projects
2458+
this.createAncestorConfiguredProjects(info, project);
24472459
}
24482460
}
24492461

@@ -2493,6 +2505,34 @@ namespace ts.server {
24932505
return { configFileName, configFileErrors };
24942506
}
24952507

2508+
/**
2509+
* Traverse till project Root and create those configured projects
2510+
*/
2511+
private createAncestorConfiguredProjects(info: ScriptInfo, project: ConfiguredProject) {
2512+
if (!project.containsScriptInfo(info) || !project.getCompilerOptions().composite) return;
2513+
const configPath = this.toPath(project.canonicalConfigFilePath);
2514+
const configInfo: OriginalFileInfo = {
2515+
fileName: project.getConfigFilePath(),
2516+
path: configPath,
2517+
openInfoPathForConfigFile: info.path
2518+
};
2519+
2520+
// Go create all configured projects till project root
2521+
while (true) {
2522+
const configFileName = this.getConfigFileNameForFile(configInfo);
2523+
if (!configFileName) return;
2524+
2525+
// TODO: may be we should create only first project and then once its loaded,
2526+
// do pending search if this is composite ?
2527+
const ancestor = this.findConfiguredProjectByProjectName(configFileName) ||
2528+
this.createConfiguredProjectWithDelayLoad(configFileName, `Project possibly referencing default composite project ${project.getProjectName()} of open file ${info.fileName}`);
2529+
ancestor.setPotentialProjectRefence(configPath);
2530+
2531+
configInfo.fileName = configFileName;
2532+
configInfo.path = this.toPath(configFileName);
2533+
}
2534+
}
2535+
24962536
private removeOrphanConfiguredProjects() {
24972537
const toRemoveConfiguredProjects = cloneMap(this.configuredProjects);
24982538

@@ -2506,15 +2546,11 @@ namespace ts.server {
25062546
markOriginalProjectsAsUsed(project);
25072547
}
25082548
else {
2509-
// If the configured project for project reference has more than zero references, keep it alive
2510-
project.forEachResolvedProjectReference(ref => {
2511-
if (ref) {
2512-
const refProject = this.configuredProjects.get(ref.sourceFile.path);
2513-
if (refProject && refProject.hasOpenRef()) {
2514-
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
2515-
}
2516-
}
2517-
});
2549+
project.forEachResolvedProjectReference(
2550+
resolvedRef => markProjectAsUsedIfReferencedConfigWithOpenRef(project, resolvedRef && this.configuredProjects.get(resolvedRef.sourceFile.path)),
2551+
projectRef => markProjectAsUsedIfReferencedConfigWithOpenRef(project, this.configuredProjects.get(this.toPath(projectRef.path))),
2552+
potentialProjectRef => markProjectAsUsedIfReferencedConfigWithOpenRef(project, this.configuredProjects.get(potentialProjectRef))
2553+
);
25182554
}
25192555
});
25202556

@@ -2526,6 +2562,13 @@ namespace ts.server {
25262562
project.originalConfiguredProjects.forEach((_value, configuredProjectPath) => toRemoveConfiguredProjects.delete(configuredProjectPath));
25272563
}
25282564
}
2565+
2566+
function markProjectAsUsedIfReferencedConfigWithOpenRef(project: ConfiguredProject, refProject: ConfiguredProject | undefined) {
2567+
if (refProject && refProject.hasOpenRef()) {
2568+
toRemoveConfiguredProjects.delete(project.canonicalConfigFilePath);
2569+
return true;
2570+
}
2571+
}
25292572
}
25302573

25312574
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {

src/server/project.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,10 +1352,14 @@ namespace ts.server {
13521352

13531353
private projectReferences: ReadonlyArray<ProjectReference> | undefined;
13541354

1355+
/** Portentual project references before the project is actually loaded (read config file) */
1356+
private potentialProjectReferences: Map<true> | undefined;
1357+
13551358
/*@internal*/
13561359
projectOptions?: ProjectOptions | true;
13571360

1358-
protected isInitialLoadPending: () => boolean = returnTrue;
1361+
/*@internal*/
1362+
isInitialLoadPending: () => boolean = returnTrue;
13591363

13601364
/*@internal*/
13611365
sendLoadingProjectFinish = false;
@@ -1378,6 +1382,13 @@ namespace ts.server {
13781382
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
13791383
}
13801384

1385+
filesToString(writeProjectFileNames: boolean) {
1386+
if (this.isInitialLoadPending()) {
1387+
return "\tFiles (0) InitialLoadPending\n";
1388+
}
1389+
return super.filesToString(writeProjectFileNames);
1390+
}
1391+
13811392
/**
13821393
* If the project has reload from disk pending, it reloads (and then updates graph as part of that) instead of just updating the graph
13831394
* @returns: true if set of files in the project stays the same and false - otherwise.
@@ -1421,12 +1432,30 @@ namespace ts.server {
14211432

14221433
updateReferences(refs: ReadonlyArray<ProjectReference> | undefined) {
14231434
this.projectReferences = refs;
1435+
this.potentialProjectReferences = undefined;
1436+
}
1437+
1438+
setPotentialProjectRefence(path: Path) {
1439+
// We know the composites if we have read the config file
1440+
if (this.isInitialLoadPending()) {
1441+
(this.potentialProjectReferences || (this.potentialProjectReferences = createMap())).set(path, true);
1442+
}
14241443
}
14251444

14261445
/*@internal*/
1427-
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined {
1446+
forEachResolvedProjectReference<T>(
1447+
cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined,
1448+
cbProjectRef: (projectReference: ProjectReference) => T | undefined,
1449+
cbPotentialProjectRef: (path: Path) => T | undefined
1450+
): T | undefined {
14281451
const program = this.getCurrentProgram();
1429-
return program && program.forEachResolvedProjectReference(cb);
1452+
if (program) {
1453+
return program.forEachResolvedProjectReference(cb);
1454+
}
1455+
if (this.isInitialLoadPending()) {
1456+
return this.potentialProjectReferences && forEachKey(this.potentialProjectReferences, cbPotentialProjectRef);
1457+
}
1458+
return forEach(this.projectReferences, cbProjectRef);
14301459
}
14311460

14321461
/*@internal*/

0 commit comments

Comments
 (0)