-
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 |
---|---|---|
|
@@ -303,6 +303,31 @@ namespace ts.server { | |
} | ||
} | ||
|
||
/* @internal */ | ||
export const enum WatchType { | ||
ConfigFilePath = "Config file for the program", | ||
MissingFilePath = "Missing file from program", | ||
WildCardDirectories = "Wild card directory", | ||
TypeRoot = "Type root of the project", | ||
ClosedScriptInfo = "Closed Script info" | ||
} | ||
|
||
/* @internal */ | ||
export const enum WatcherCloseReason { | ||
ProjectClose = "Project close", | ||
NotNeeded = "After project update isnt required any more", | ||
FileCreated = "File got created", | ||
RecursiveChanged = "Recursive changed for the watch", | ||
ProjectReloadHitMaxSize = "Project reloaded and hit the max file size capacity", | ||
OrphanScriptInfoWithChange = "Orphan script info, Detected change in file thats not needed any more", | ||
OrphanScriptInfo = "Removing Orphan script info as part of cleanup", | ||
FileDeleted = "File was deleted", | ||
FileOpened = "File opened" | ||
} | ||
|
||
/* @internal */ | ||
export type ServerDirectoryWatcherCallback = (path: NormalizedPath) => void; | ||
|
||
export interface ProjectServiceOptions { | ||
host: ServerHost; | ||
logger: Logger; | ||
|
@@ -599,7 +624,7 @@ namespace ts.server { | 8000|
if (!info.isScriptOpen()) { | ||
if (info.containingProjects.length === 0) { | ||
// Orphan script info, remove it as we can always reload it on next open file request | ||
info.stopWatcher(); | ||
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfoWithChange); | ||
this.filenameToScriptInfo.delete(info.path); | ||
} | ||
else { | ||
|
@@ -613,9 +638,7 @@ namespace ts.server { | |
} | ||
|
||
private handleDeletedFile(info: ScriptInfo) { | ||
this.logger.info(`${info.fileName} deleted`); | ||
|
||
info.stopWatcher(); | ||
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileDeleted); | ||
|
||
// TODO: handle isOpen = true case | ||
|
||
|
@@ -644,7 +667,8 @@ namespace ts.server { | |
} | ||
} | ||
|
||
private onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { | ||
/* @internal */ | ||
onTypeRootFileChanged(project: ConfiguredProject, fileName: NormalizedPath) { | ||
this.logger.info(`Type root file ${fileName} changed`); | ||
project.getCachedServerHost().addOrDeleteFileOrFolder(fileName); | ||
project.updateTypes(); | ||
|
@@ -667,10 +691,10 @@ namespace ts.server { | |
return; | ||
} | ||
|
||
this.logger.info(`Detected source file changes: ${fileName}`); | ||
const configFilename = project.getConfigFilePath(); | ||
this.logger.info(`Project: ${configFilename} Detected source file add/remove: ${fileName}`); | ||
|
||
const configFileSpecs = project.configFileSpecs; | ||
const configFilename = normalizePath(project.getConfigFilePath()); | ||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), project.getCachedServerHost(), this.hostConfiguration.extraFileExtensions); | ||
const errors = project.getAllProjectErrors(); | ||
if (result.fileNames.length === 0) { | ||
|
@@ -693,9 +717,7 @@ namespace ts.server { | |
} | ||
|
||
private onConfigChangedForConfiguredProject(project: ConfiguredProject, eventKind: FileWatcherEventKind) { | ||
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. Same here with 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 prefer the callback function to have control over the actions on events so that its easier to read as well as change if needed. |
||
const configFileName = project.getConfigFilePath(); | ||
if (eventKind === FileWatcherEventKind.Deleted) { | ||
this.logger.info(`Config file deleted: ${configFileName}`); | ||
this.removeProject(project); | ||
// Reload the configured projects for these open files in the project as | ||
// they could be held up by another config file somewhere in the parent directory | ||
|
@@ -704,7 +726,6 @@ namespace ts.server { | |
this.delayInferredProjectsRefresh(); | ||
} | ||
else { | ||
this.logger.info(`Config file changed: ${configFileName}`); | ||
project.pendingReload = true; | ||
this.delayUpdateProjectGraphAndInferredProjectsRefresh(project); | ||
} | ||
|
@@ -894,7 +915,7 @@ namespace ts.server { | |
this.filenameToScriptInfo.forEach(info => { | ||
if (!info.isScriptOpen() && info.containingProjects.length === 0) { | ||
// if there are not projects that include this script info - delete it | ||
info.stopWatcher(); | ||
this.stopWatchingScriptInfo(info, WatcherCloseReason.OrphanScriptInfo); | ||
this.filenameToScriptInfo.delete(info.path); | ||
} | ||
}); | ||
|
@@ -1155,22 +1176,26 @@ namespace ts.server { | |
} | ||
|
||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, cachedServerHost: CachedServerHost, clientFileName?: string) { | ||
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. Nit: Move parameters onto their own lines. 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. Its a fairly long function. I wouldn't recommend inlining unless the containing function is extremely short. |
||
const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); | ||
const languageServiceEnabled = !this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader); | ||
const project = new ConfiguredProject( | ||
configFileName, | ||
this, | ||
this.documentRegistry, | ||
projectOptions.configHasFilesProperty, | ||
projectOptions.compilerOptions, | ||
/*languageServiceEnabled*/ !sizeLimitExceeded, | ||
languageServiceEnabled, | ||
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave, | ||
cachedServerHost); | ||
this.addFilesToProjectAndUpdateGraph(project, projectOptions.files, fileNamePropertyReader, clientFileName, projectOptions.typeAcquisition, configFileErrors); | ||
|
||
project.configFileSpecs = configFileSpecs; | ||
project.watchConfigFile((project, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind)); | ||
project.watchWildcards(projectOptions.wildcardDirectories); | ||
project.watchTypeRoots((project, path) => this.onTypeRootFileChanged(project, path)); | ||
project.configFileWatcher = this.addFileWatcher(WatchType.ConfigFilePath, project, | ||
configFileName, (_fileName, eventKind) => this.onConfigChangedForConfiguredProject(project, eventKind) | ||
); | ||
if (languageServiceEnabled) { | ||
project.watchWildcards(projectOptions.wildcardDirectories); | ||
project.watchTypeRoots(); | ||
} | ||
|
||
this.configuredProjects.push(project); | ||
this.sendProjectTelemetry(project.getConfigFilePath(), project, projectOptions); | ||
|
@@ -1314,11 +1339,13 @@ namespace ts.server { | |
private updateConfiguredProject(project: ConfiguredProject, projectOptions: ProjectOptions, configFileErrors: Diagnostic[]) { | ||
if (this.exceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader)) { | ||
project.disableLanguageService(); | ||
project.stopWatchingWildCards(); | ||
project.stopWatchingWildCards(WatcherCloseReason.ProjectReloadHitMaxSize); | ||
project.stopWatchingTypeRoots(WatcherCloseReason.ProjectReloadHitMaxSize); | ||
} | ||
else { | ||
project.enableLanguageService(); | ||
project.watchWildcards(projectOptions.wildcardDirectories); | ||
project.watchTypeRoots(); | ||
} | ||
this.updateNonInferredProject(project, projectOptions.files, fileNamePropertyReader, projectOptions.compilerOptions, projectOptions.typeAcquisition, projectOptions.compileOnSave, configFileErrors); | ||
} | ||
|
@@ -1357,11 +1384,21 @@ namespace ts.server { | |
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName)); | ||
} | ||
|
||
watchClosedScriptInfo(info: ScriptInfo) { | ||
private watchClosedScriptInfo(info: ScriptInfo) { | ||
Debug.assert(!info.fileWatcher); | ||
// do not watch files with mixed content - server doesn't know how to interpret it | ||
if (!info.hasMixedContent) { | ||
const { fileName } = info; | ||
info.setWatcher(this.host.watchFile(fileName, (_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind))); | ||
info.fileWatcher = this.addFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, fileName, | ||
(_fileName, eventKind) => this.onSourceFileChanged(fileName, eventKind) | ||
); | ||
} | ||
} | ||
|
||
private stopWatchingScriptInfo(info: ScriptInfo, reason: WatcherCloseReason) { | ||
if (info.fileWatcher) { | ||
this.closeFileWatcher(WatchType.ClosedScriptInfo, /*project*/ undefined, info.fileName, info.fileWatcher, reason); | ||
info.fileWatcher = undefined; | ||
} | ||
} | ||
|
||
|
@@ -1387,7 +1424,7 @@ namespace ts.server { | |
} | ||
if (info) { | ||
if (openedByClient && !info.isScriptOpen()) { | ||
info.stopWatcher(); | ||
this.stopWatchingScriptInfo(info, WatcherCloseReason.FileOpened); | ||
info.open(fileContent); | ||
if (hasMixedContent) { | ||
info.registerFileUpdate(); | ||
|
@@ -1408,7 +1445,6 @@ namespace ts.server { | |
return this.filenameToScriptInfo.get(fileName); | ||
} | ||
|
||
|
||
setHostConfiguration(args: protocol.ConfigureRequestArguments) { | ||
if (args.file) { | ||
const info = this.getScriptInfoForNormalizedPath(toNormalizedPath(args.file)); | ||
|
@@ -1436,6 +1472,37 @@ namespace ts.server { | |
} | ||
} | ||
|
||
/* @internal */ | ||
closeFileWatcher(watchType: WatchType, project: Project, file: string, watcher: FileWatcher, reason: WatcherCloseReason) { | ||
this.logger.info(`FileWatcher:: Close: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType} Reason: ${reason}`); | ||
watcher.close(); | ||
} | ||
|
||
/* @internal */ | ||
addFileWatcher(watchType: WatchType, project: Project, file: string, cb: FileWatcherCallback) { | ||
this.logger.info(`FileWatcher:: Added: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`); | ||
return this.host.watchFile(file, (fileName, eventKind) => { | ||
this.logger.info(`FileWatcher:: File ${FileWatcherEventKind[eventKind]}: ${file} Project: ${project ? project.getProjectName() : ""} WatchType: ${watchType}`); | ||
cb(fileName, eventKind); | ||
}); | ||
} | ||
|
||
/* @internal */ | ||
closeDirectoryWatcher(watchType: WatchType, project: Project, directory: string, watcher: FileWatcher, recursive: boolean, reason: WatcherCloseReason) { | ||
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Close: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType} Reason: ${reason}`); | ||
watcher.close(); | ||
} | ||
|
||
/* @internal */ | ||
addDirectoryWatcher(watchType: WatchType, project: Project, directory: string, cb: ServerDirectoryWatcherCallback, recursive: boolean) { | ||
this.logger.info(`DirectoryWatcher ${recursive ? "recursive" : ""}:: Added: ${directory} Project: ${project.getProjectName()} WatchType: ${watchType}`); | ||
return this.host.watchDirectory(directory, fileName => { | ||
const path = toNormalizedPath(getNormalizedAbsolutePath(fileName, directory)); | ||
this.logger.info(`DirectoryWatcher:: EventOn: ${directory} Trigger: ${fileName} Path: ${path} Project: ${project.getProjectName()} WatchType: ${watchType}`); | ||
cb(path); | ||
}, recursive); | ||
} | ||
|
||
closeLog() { | ||
this.logger.close(); | ||
} | ||
|
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.
"wildcard" is one word.