-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Watch improvements in tsserver #17269
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
21ad26b
ae33ae8
75698a8
f154910
0e44367
2a63827
96ffd53
6bd42b8
df6f75b
9ff9476
62871cc
48c6513
19a6a00
68def1b
029b1f2
f338a70
8fedcf7
e568976
048e67c
0365901
404aa8f
71d79c6
f12980d
00011a5
0572b15
62663a1
dcbd7b1
62ef6b1
2439e7a
ff34a77
1155c37
ae87838
802e283
499fabc
273569f
94a589b
ef5935b
e068475
6237b22
9b18f7b
85b9254
69e5abd
c814d8e
2dd6aed
89c61e7
bb91b32
46e3d1c
031a637
0d5e6c9
2762232
65a6ee0
d55150c
8dc6248
7474ba7
6385f7e
65521bc
27988bf
02b8a7d
f723beb
b071a86
8db05c2
594482d
d0a23bb
59d07dc
9895082
f1b1b12
136b091
6bf9133
a99c04e
b66b752
da0d374
e639ceb
8deef58
c425128
d217bec
60e2e68
84b2e23
3908325
7173da2
e500be2
6227a36
55931c4
e65df12
e711238
3b85f3f
ea95f3b
9e570c3
4c79033
5aafd3f
17565d8
10ea5bf
254e393
a3b9467
16cf7c4
d7ce95d
345f36d
2b97b2c
8d5d4c2
9e5e20c
13aafa2
6c61293
7b2bab5
54f64a1
0ff160f
e6eede1
2a5d954
680994e
c8e711c
29e93c3
b179cd1
67f9533
de28d02
5739b68
fdb104b
aea8630
b536f9d
4f7c0e5
cf72f2a
23acff5
14febe2
38f3a2b
68d3605
9e08cae
835153b
898559b
7f969e8
4bb4711
8ac01d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1289,8 +1289,7 @@ namespace ts.server { | |
} | ||
|
||
const configFilePath = project instanceof server.ConfiguredProject && project.getConfigFilePath(); | ||
const base = getBaseFileName(configFilePath); | ||
return base === "tsconfig.json" || base === "jsconfig.json" ? base : "other"; | ||
return getBaseConfigFileName(configFilePath) || "other"; | ||
} | ||
|
||
function convertTypeAcquisition({ enable, include, exclude }: TypeAcquisition): ProjectInfoTypeAcquisitionData { | ||
|
@@ -1344,13 +1343,14 @@ namespace ts.server { | |
|
||
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, clientFileName?: string) { | ||
const projectRootFilesMap = project.getRootFilesMap(); | ||
const newRootScriptInfoMap: Map<ProjectRoot> = createMap<ProjectRoot>(); | ||
const newRootScriptInfoMap = createMap<ProjectRoot>(); | ||
|
||
for (const f of newUncheckedFiles) { | ||
const newRootFile = propertyReader.getFileName(f); | ||
const normalizedPath = toNormalizedPath(newRootFile); | ||
let scriptInfo: ScriptInfo | NormalizedPath; | ||
let path: Path; | ||
// Use the project's lsHost so that it can use caching instead of reaching to disk for the query | ||
if (!project.lsHost.fileExists(newRootFile)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why reach through to |
||
path = normalizedPathToPath(normalizedPath, this.currentDirectory, this.toCanonicalFileName); | ||
const existingValue = projectRootFilesMap.get(path); | ||
|
@@ -1378,7 +1378,7 @@ namespace ts.server { | |
newRootScriptInfoMap.set(path, scriptInfo); | ||
} | ||
|
||
// project's root file map size is always going to be larger than new roots map | ||
// project's root file map size is always going to be same or larger than new roots map | ||
// as we have already all the new files to the project | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have already added all? |
||
if (projectRootFilesMap.size > newRootScriptInfoMap.size) { | ||
projectRootFilesMap.forEach((value, path) => { | ||
|
@@ -1388,12 +1388,14 @@ namespace ts.server { | |
6D40 td> | } | |
else { | ||
projectRootFilesMap.delete(path); | ||
project.markAsDirty(); | ||
} | ||
} | ||
}); | ||
} | ||
project.markAsDirty(); // Just to ensure that even if root files dont change, the changes to the non root file are picked up | ||
|
||
// Just to ensure that even if root files dont change, the changes to the non root file are picked up, | ||
// mark the project as dirty unconditionally | ||
project.markAsDirty(); | ||
} | ||
|
||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) { | ||
|
@@ -1408,38 +1410,22 @@ namespace ts.server { | |
|
||
/** | ||
* Read the config file of the project again and update the project | ||
* @param project | ||
*/ | ||
/* @internal */ | ||
reloadConfiguredProject(project: ConfiguredProject) { | ||
// At this point, there is no reason to not have configFile in the host | ||
|
||
// note: the returned "success" is true does not mean the "configFileErrors" is empty. | ||
// because we might have tolerated the errors and kept going. So always return the configFileErrors | ||
// regardless the "success" here is true or not. | ||
const host = project.getCachedServerHost(); | ||
|
||
// Clear the cache since we are reloading the project from disk | ||
host.clearCache(); | ||
const configFileName = project.getConfigFilePath(); | ||
this.logger.info(`Reloading configured project ${configFileName}`); | ||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); | ||
project.configFileSpecs = configFileSpecs; | ||
this.updateConfiguredProject(project, projectOptions, configFileErrors); | ||
|
||
if (!this.eventHandler) { | ||
return; | ||
9E88 } | ||
|
||
this.eventHandler(<ConfigFileDiagEvent>{ | ||
eventName: ConfigFileDiagEvent, | ||
data: { configFileName, diagnostics: project.getGlobalProjectErrors() || [], triggerFile: configFileName } | ||
}); | ||
} | ||
// Read updated contents from disk | ||
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host); | ||
|
||
/** | ||
* Updates the configured project with updated config file contents | ||
* @param project | ||
*/ | ||
private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) { | ||
// Update the project | ||
project.configFileSpecs = configFileSpecs; | ||
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { | ||
project.disableLanguageService(); | ||
project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize); | ||
|
@@ -1451,6 +1437,15 @@ namespace ts.server { | |
project.watchTypeRoots(); | ||
} | ||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); | ||
|
||
if (!this.eventHandler) { | ||
return; | ||
} | ||
|
||
this.eventHandler(<ConfigFileDiagEvent>{ | ||
eventName: ConfigFileDiagEvent, | ||
data: { configFileName, diagnostics: project.getGlobalProjectErrors() || [], triggerFile: configFileName } | ||
}); | ||
} | ||
|
||
/*@internal*/ | ||
|
@@ -1587,8 +1582,8 @@ namespace ts.server { | |
} | ||
if (args.extraFileExtensions) { | ||
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions; | ||
// We need to update the projects because of we might interprete more/less files | ||
// depending on whether extra files extenstions are either added or removed | ||
// We need to update the project structures again as it is possible that existing | ||
// project structure could have more or less files depending on extensions permitted | ||
this.reloadProjects(); | ||
this.logger.info("Host file extension mappings updated"); | ||
} | ||
|
@@ -1664,7 +1659,7 @@ namespace ts.server { | |
* If the there is no existing project it just opens the configured project for the config file | ||
*/ | ||
private reloadConfiguredsProjectForFiles(openFiles: ScriptInfo[], delayReload: boolean) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const mapUpdatedProjects = createMap<true>(); | ||
const updatedProjects = createMap<true>(); | ||
// try to reload config file for all open files | ||
for (const info of openFiles) { | ||
// This tries to search for a tsconfig.json for the given file. If we found it, | ||
|
@@ -1673,32 +1668,35 @@ namespace ts.server { | |
// otherwise we create a new one. | ||
const configFileName = this.getConfigFileNameForFile(info); | ||
if (configFileName) { | ||
let project = this.findConfiguredProjectByProjectName(configFileName); | ||
const project = this.findConfiguredProjectByProjectName(configFileName); | ||
if (!project) { | ||
project = this.createConfiguredProject(configFileName, info.fileName); | ||
mapUpdatedProjects.set(configFileName, true); | ||
this.createConfiguredProject(configFileName, info.fileName); | ||
updatedProjects.set(configFileName, true); | ||
} | ||
else if (!mapUpdatedProjects.has(configFileName)) { | ||
else if (!updatedProjects.has(configFileName)) { | ||
if (delayReload) { | ||
project.pendingReload = true; | ||
this.delayUpdateProjectGraph(project); | ||
} | ||
else { | ||
this.reloadConfiguredProject(project); | ||
} | ||
mapUpdatedProjects.set(configFileName, true); | ||
updatedProjects.set(configFileName, true); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* - script info can be never migrate to state - root file in inferred project, this is only a starting point | ||
* - if script info has more that one containing projects - it is not a root file in inferred project because: | ||
* - references in inferred project supercede the root part | ||
* - root/reference in non-inferred project beats root in inferred project | ||
* Remove the root of inferred project if script info is part of another project | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could really use an explanation of how/why this happens |
||
private removeRootOfInferredProjectIfNowPartOfOtherProject(info: ScriptInfo) { | ||
// If the script info is root of inferred project, it could only be first containing project | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've read this and I still don't understand why only the first project needs removing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm check if this helps. I can explain it further if this is not still clear // If the script info is root of inferred project, it could only be first containing project
// since info is added as root to the inferred project only when there are no other projects containing it
// So when it is root of the inferred project and after project structure updates its now part
// of multiple project it needs to be removed from that inferred project because:
// - references in inferred project supercede the root part
// - root / reference in non - inferred project beats root in inferred project
// eg. say this is structure /a/b/a.ts /a/b/c.ts where c.ts references a.ts
// When a.ts is opened, since there is no configured project/external project a.ts can be part of
// a.ts is added as root to inferred project.
// Now at time of opening c.ts, c.ts is also not aprt of any existing project,
// so it will be added to inferred project as a root. (for sake of this example assume single inferred project is false)
// So at this poing a.ts is part of first inferred project and second inferred project (of which c.ts is root)
// And hence it needs to be removed from the first inferred project. |
||
// since info is added to inferred project and made root only when there are no other projects containing it | ||
// So even if it is root of the inferred project and after project structure updates its now part | ||
// of multiple project it needs to be removed from that inferred project because: | ||
// - references in inferred project supercede the root part | ||
// - root / reference in non - inferred project beats root in inferred project | ||
if (info.containingProjects.length > 1 && | ||
info.containingProjects[0].projectKind === ProjectKind.Inferred && | ||
info.containingProjects[0].isRoot(info)) { | ||
|
@@ -1728,8 +1726,8 @@ namespace ts.server { | |
if (info.containingProjects.length === 0) { | ||
this.assignScriptInfoToInferredProject(info); | ||
} | ||
// Or remove the root of inferred project if is referenced in more than one projects | ||
else { | ||
// Or remove the root of inferred project if is referenced in more than one projects | ||
this.removeRootOfInferredProjectIfNowPartOfOtherProject(info); | ||
} | ||
} | ||
|
@@ -1848,7 +1846,7 @@ namespace ts.server { | |
if (!this.changedFiles) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would make a good helper function, |
||
this.changedFiles = [scriptInfo]; | ||
} | ||
else if (this.changedFiles.indexOf(scriptInfo) < 0) { | ||
else if (!contains(this.changedFiles, scriptInfo)) { | ||
this.changedFiles.push(scriptInfo); | ||
} | ||
} | ||
|
@@ -2023,8 +2021,7 @@ namespace ts.server { | |
const rootFiles: protocol.ExternalFile[] = []; | ||
for (const file of proj.rootFiles) { | ||
const normalized = toNormalizedPath(file.fileName); | ||
const baseFileName = getBaseFileName(normalized); | ||
if (baseFileName === "tsconfig.json" || baseFileName === "jsconfig.json") { | ||
if (getBaseConfigFileName(normalized)) { | ||
if (this.host.fileExists(normalized)) { | ||
(tsConfigFiles || (tsConfigFiles = [])).push(normalized); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Use
ReadonlyArray
where applicable, such as fornewUncheckedFiles
.