pass program update level to getExternalFiles in tsserver plugins so plugins can make decision about their cache based on that (#56047)

Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
This commit is contained in:
Sheetal Nandi
2023-10-09 16:05:11 -07:00
committed by GitHub
co-authored by Andrew Branch
parent d0d406747d
commit 3516ca96e5
9 changed files with 213 additions and 115 deletions
+30 -30
View File
@@ -14,7 +14,6 @@ import {
CommandLineOption,
CompilerOptions,
CompletionInfo,
ConfigFileProgramReloadLevel,
contains,
containsPath,
convertCompilerOptionsForTelemetry,
@@ -95,6 +94,7 @@ import {
PerformanceEvent,
PluginImport,
PollingInterval,
ProgramUpdateLevel,
ProjectPackageJsonInfo,
ProjectReference,
ReadMapFile,
@@ -874,7 +874,7 @@ export interface ParsedConfig {
* true if watchedDirectories need to be updated as per parsedCommandLine's updated watched directories
*/
watchedDirectoriesStale?: boolean;
reloadLevel?: ConfigFileProgramReloadLevel.Partial | ConfigFileProgramReloadLevel.Full;
updateLevel?: ProgramUpdateLevel.RootNamesAndUpdate | ProgramUpdateLevel.Full;
}
function createProjectNameFactoryWithCounter(nameFactory: (counter: number) => string) {
@@ -1671,7 +1671,7 @@ export class ProjectService {
) return;
// Reload is pending, do the reload
if (config.reloadLevel !== ConfigFileProgramReloadLevel.Full) config.reloadLevel = ConfigFileProgramReloadLevel.Partial;
if (config.updateLevel !== ProgramUpdateLevel.Full) config.updateLevel = ProgramUpdateLevel.RootNamesAndUpdate;
config.projects.forEach((watchWildcardDirectories, projectCanonicalPath) => {
if (!watchWildcardDirectories) return;
const project = this.getConfiguredProjectByCanonicalConfigFilePath(projectCanonicalPath);
@@ -1679,23 +1679,23 @@ export class ProjectService {
// Load root file names for configured project with the config file name
// But only schedule update if project references this config file
const reloadLevel = configuredProjectForConfig === project ? ConfigFileProgramReloadLevel.Partial : ConfigFileProgramReloadLevel.None;
if (project.pendingReload !== undefined && project.pendingReload > reloadLevel) return;
const updateLevel = configuredProjectForConfig === project ? ProgramUpdateLevel.RootNamesAndUpdate : ProgramUpdateLevel.Update;
if (project.pendingUpdateLevel !== undefined && project.pendingUpdateLevel > updateLevel) return;
// don't trigger callback on open, existing files
if (this.openFiles.has(fileOrDirectoryPath)) {
const info = Debug.checkDefined(this.getScriptInfoForPath(fileOrDirectoryPath));
if (info.isAttached(project)) {
const loadLevelToSet = Math.max(reloadLevel, project.openFileWatchTriggered.get(fileOrDirectoryPath) || ConfigFileProgramReloadLevel.None) as ConfigFileProgramReloadLevel;
const loadLevelToSet = Math.max(updateLevel, project.openFileWatchTriggered.get(fileOrDirectoryPath) || ProgramUpdateLevel.Update) as ProgramUpdateLevel;
project.openFileWatchTriggered.set(fileOrDirectoryPath, loadLevelToSet);
}
else {
project.pendingReload = reloadLevel;
project.pendingUpdateLevel = updateLevel;
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
}
}
else {
project.pendingReload = reloadLevel;
project.pendingUpdateLevel = updateLevel;
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
}
});
@@ -1708,12 +1708,12 @@ export class ProjectService {
}
/** @internal */
private delayUpdateProjectsFromParsedConfigOnConfigFileChange(canonicalConfigFilePath: NormalizedPath, reloadReason: string) {
private delayUpdateProjectsFromParsedConfigOnConfigFileChange(canonicalConfigFilePath: NormalizedPath, loadReason: string) {
const configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (!configFileExistenceInfo?.config) return false;
let scheduledAnyProjectUpdate = false;
// Update projects watching cached config
configFileExistenceInfo.config.reloadLevel = ConfigFileProgramReloadLevel.Full;
configFileExistenceInfo.config.updateLevel = ProgramUpdateLevel.Full;
configFileExistenceInfo.config.projects.forEach((_watchWildcardDirectories, projectCanonicalPath) => {
const project = this.getConfiguredProjectByCanonicalConfigFilePath(projectCanonicalPath);
@@ -1723,8 +1723,8 @@ export class ProjectService {
if (projectCanonicalPath === canonicalConfigFilePath) {
// Skip refresh if project is not yet loaded
if (project.isInitialLoadPending()) return;
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reloadReason;
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
project.pendingUpdateReason = loadReason;
this.delayUpdateProjectGraph(project);
}
else {
@@ -1922,11 +1922,11 @@ export class ProjectService {
// If project had open file affecting
// Reload the root Files from config if its not already scheduled
const reloadLevel = p.openFileWatchTriggered.get(info.path);
if (reloadLevel !== undefined) {
const updateLevel = p.openFileWatchTriggered.get(info.path);
if (updateLevel !== undefined) {
p.openFileWatchTriggered.delete(info.path);
if (p.pendingReload !== undefined && p.pendingReload < reloadLevel) {
p.pendingReload = reloadLevel;
if (p.pendingUpdateLevel !== undefined && p.pendingUpdateLevel < updateLevel) {
p.pendingUpdateLevel = updateLevel;
p.markFileAsDirty(info.path);
}
}
@@ -2438,7 +2438,7 @@ export class ProjectService {
configFileExistenceInfo.config = {
cachedDirectoryStructureHost: createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!,
projects: new Map(),
reloadLevel: ConfigFileProgramReloadLevel.Full,
updateLevel: ProgramUpdateLevel.Full,
};
}
@@ -2457,8 +2457,8 @@ export class ProjectService {
/** @internal */
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath, reason: string) {
const project = this.createConfiguredProject(configFileName);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
project.pendingUpdateReason = reason;
return project;
}
@@ -2521,7 +2521,7 @@ export class ProjectService {
this.watchWildcards(configFilename, configFileExistenceInfo, project);
}
project.enablePluginsWithOptions(compilerOptions);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles(ProgramUpdateLevel.Full));
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave, parsedCommandLine.watchOptions);
tracing?.pop();
}
@@ -2529,8 +2529,8 @@ export class ProjectService {
/** @internal */
ensureParsedConfigUptoDate(configFilename: NormalizedPath, canonicalConfigFilePath: NormalizedPath, configFileExistenceInfo: ConfigFileExistenceInfo, forProject: ConfiguredProject): ConfigFileExistenceInfo {
if (configFileExistenceInfo.config) {
if (!configFileExistenceInfo.config.reloadLevel) return configFileExistenceInfo;
if (configFileExistenceInfo.config.reloadLevel === ConfigFileProgramReloadLevel.Partial) {
if (!configFileExistenceInfo.config.updateLevel) return configFileExistenceInfo;
if (configFileExistenceInfo.config.updateLevel === ProgramUpdateLevel.RootNamesAndUpdate) {
this.reloadFileNamesOfParsedConfig(configFilename, configFileExistenceInfo.config);
return configFileExistenceInfo;
}
@@ -2580,7 +2580,7 @@ export class ProjectService {
else {
configFileExistenceInfo.config.parsedCommandLine = parsedCommandLine;
configFileExistenceInfo.config.watchedDirectoriesStale = true;
configFileExistenceInfo.config.reloadLevel = undefined;
configFileExistenceInfo.config.updateLevel = undefined;
}
// If watch options different than older options when setting for the first time, update the config file watcher
@@ -2761,14 +2761,14 @@ export class ProjectService {
reloadFileNamesOfConfiguredProject(project: ConfiguredProject) {
const fileNames = this.reloadFileNamesOfParsedConfig(project.getConfigFilePath(), this.configFileExistenceInfoCache.get(project.canonicalConfigFilePath)!.config!);
project.updateErrorOnNoInputFiles(fileNames);
this.updateNonInferredProjectFiles(project, fileNames.concat(project.getExternalFiles()), fileNamePropertyReader);
this.updateNonInferredProjectFiles(project, fileNames.concat(project.getExternalFiles(ProgramUpdateLevel.RootNamesAndUpdate)), fileNamePropertyReader);
return project.updateGraph();
}
/** @internal */
private reloadFileNamesOfParsedConfig(configFileName: NormalizedPath, config: ParsedConfig) {
if (config.reloadLevel === undefined) return config.parsedCommandLine!.fileNames;
Debug.assert(config.reloadLevel === ConfigFileProgramReloadLevel.Partial);
if (config.updateLevel === undefined) return config.parsedCommandLine!.fileNames;
Debug.assert(config.updateLevel === ProgramUpdateLevel.RootNamesAndUpdate);
const configFileSpecs = config.parsedCommandLine!.options.configFile!.configFileSpecs!;
const fileNames = getFileNamesFromConfigSpecs(
configFileSpecs,
@@ -3398,7 +3398,7 @@ export class ProjectService {
this.configuredProjects.forEach(project => {
if (
project.hasExternalProjectRef() &&
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
project.pendingUpdateLevel === ProgramUpdateLevel.Full &&
!this.pendingProjectUpdates.has(project.getProjectName())
) {
project.updateGraph();
@@ -3468,7 +3468,7 @@ export class ProjectService {
// Ensure everything is reloaded for cached configs
this.configFileExistenceInfoCache.forEach(info => {
if (info.config) info.config.reloadLevel = ConfigFileProgramReloadLevel.Full;
if (info.config) info.config.updateLevel = ProgramUpdateLevel.Full;
});
// Reload Projects
@@ -3517,8 +3517,8 @@ export class ProjectService {
if (!updatedProjects.has(project.canonicalConfigFilePath)) {
updatedProjects.set(project.canonicalConfigFilePath, true);
if (delayReload) {
project.pendingReload = ConfigFileProgramReloadLevel.Full;
project.pendingReloadReason = reason;
project.pendingUpdateLevel = ProgramUpdateLevel.Full;
project.pendingUpdateReason = reason;
if (clearSemanticCache) this.clearSemanticCache(project);
this.delayUpdateProjectGraph(project);
}
+18 -18
View File
@@ -17,7 +17,6 @@ import {
CompilerHost,
CompilerOptions,
concatenate,
ConfigFileProgramReloadLevel,
containsPath,
createCacheableExportInfoMap,
createLanguageService,
@@ -98,6 +97,7 @@ import {
PluginImport,
PollingInterval,
Program,
ProgramUpdateLevel,
ProjectPackageJsonInfo,
ProjectReference,
removeFileExtension,
@@ -254,7 +254,7 @@ export interface PluginCreateInfo {
export interface PluginModule {
create(createInfo: PluginCreateInfo): LanguageService;
getExternalFiles?(proj: Project): string[];
getExternalFiles?(proj: Project, updateLevel: ProgramUpdateLevel): string[];
onConfigurationChanged?(config: any): void;
}
@@ -1027,11 +1027,11 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
return { ...newTypeAcquisition, include: this.removeExistingTypings(newTypeAcquisition.include) };
}
getExternalFiles(): SortedReadonlyArray<string> {
getExternalFiles(updateLevel?: ProgramUpdateLevel): SortedReadonlyArray<string> {
return sort(flatMap(this.plugins, plugin => {
if (typeof plugin.module.getExternalFiles !== "function") return;
try {
return plugin.module.getExternalFiles(this);
return plugin.module.getExternalFiles(this, updateLevel || ProgramUpdateLevel.Update);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
@@ -1892,10 +1892,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
}
// compute and return the difference
const lastReportedFileNames = this.lastReportedFileNames;
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
const externalFiles = this.externalFiles?.map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName: toNormalizedPath(f),
isSourceOfProjectReferenceRedirect: false,
}));
})) || emptyArray;
const currentFiles = arrayToMap(
this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo).concat(externalFiles),
info => info.fileName,
@@ -1945,10 +1945,10 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
else {
// unknown version - return everything
const projectFileNames = this.getFileNamesWithRedirectInfo(!!includeProjectReferenceRedirectInfo);
const externalFiles = this.getExternalFiles().map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
const externalFiles = this.externalFiles?.map((f): protocol.FileWithProjectReferenceRedirectInfo => ({
fileName: toNormalizedPath(f),
isSourceOfProjectReferenceRedirect: false,
}));
})) || emptyArray;
const allFiles = projectFileNames.concat(externalFiles);
this.lastReportedFileNames = arrayToMap(
allFiles,
@@ -2679,12 +2679,12 @@ export class AutoImportProviderProject extends Project {
*/
export class ConfiguredProject extends Project {
/** @internal */
pendingReload: ConfigFileProgramReloadLevel | undefined;
pendingUpdateLevel: ProgramUpdateLevel | undefined;
/** @internal */
pendingReloadReason: string | undefined;
pendingUpdateReason: string | undefined;
/** @internal */
openFileWatchTriggered = new Map<string, ConfigFileProgramReloadLevel>();
openFileWatchTriggered = new Map<string, ProgramUpdateLevel>();
/** @internal */
canConfigFileJsonReportNoInputFiles = false;
@@ -2775,18 +2775,18 @@ export class ConfiguredProject extends Project {
override updateGraph(): boolean {
const isInitialLoad = this.isInitialLoadPending();
this.isInitialLoadPending = returnFalse;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
const updateLevel = this.pendingUpdateLevel;
this.pendingUpdateLevel = ProgramUpdateLevel.Update;
let result: boolean;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
switch (updateLevel) {
case ProgramUpdateLevel.RootNamesAndUpdate:
this.openFileWatchTriggered.clear();
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
break;
case ConfigFileProgramReloadLevel.Full:
case ProgramUpdateLevel.Full:
this.openFileWatchTriggered.clear();
const reason = Debug.checkDefined(this.pendingReloadReason);
this.pendingReloadReason = undefined;
const reason = Debug.checkDefined(this.pendingUpdateReason);
this.pendingUpdateReason = undefined;
this.projectService.reloadConfiguredProject(this, reason, isInitialLoad, /*clearSemanticCache*/ false);
result = true;
break;