When opening a file, if it is using existing project, there is no need to update the project by re-reading the config file
This will improve the opening file perf for file opens from same config project
This commit is contained in:
parent
bffde588cc
commit
21ad26b6ff
3 changed files with 88 additions and 74 deletions
|
@ -94,8 +94,8 @@ namespace ts.projectSystem {
|
||||||
checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]);
|
checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]);
|
||||||
|
|
||||||
host.reloadFS([file1, file2, config]);
|
host.reloadFS([file1, file2, config]);
|
||||||
|
host.triggerFileWatcherCallback(file2.path, FileWatcherEventKind.Created);
|
||||||
|
|
||||||
projectService.openClientFile(file1.path);
|
|
||||||
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
projectService.checkNumberOfProjects({ configuredProjects: 1 });
|
||||||
checkProjectErrors(projectService.synchronizeProjectList([])[0], []);
|
checkProjectErrors(projectService.synchronizeProjectList([])[0], []);
|
||||||
});
|
});
|
||||||
|
|
|
@ -211,20 +211,6 @@ namespace ts.server {
|
||||||
extraFileExtensions?: JsFileExtensionInfo[];
|
extraFileExtensions?: JsFileExtensionInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfigFileConversionResult {
|
|
||||||
success: boolean;
|
|
||||||
configFileErrors?: Diagnostic[];
|
|
||||||
|
|
||||||
projectOptions?: ProjectOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OpenConfigFileResult {
|
|
||||||
success: boolean;
|
|
||||||
errors?: Diagnostic[];
|
|
||||||
|
|
||||||
project?: ConfiguredProject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OpenConfiguredProjectResult {
|
export interface OpenConfiguredProjectResult {
|
||||||
configFileName?: NormalizedPath;
|
configFileName?: NormalizedPath;
|
||||||
configFileErrors?: Diagnostic[];
|
configFileErrors?: Diagnostic[];
|
||||||
|
@ -862,42 +848,44 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function tries to search for a tsconfig.json for the given file. If we found it,
|
* This function tries to search for a tsconfig.json for the given file.
|
||||||
* we first detect if there is already a configured project created for it: if so, we re-read
|
* This is different from the method the compiler uses because
|
||||||
* the tsconfig file content and update the project; otherwise we create a new one.
|
* the compiler can assume it will always start searching in the
|
||||||
|
* current directory (the directory in which tsc was invoked).
|
||||||
|
* The server must start searching from the directory containing
|
||||||
|
* the newly opened file.
|
||||||
*/
|
*/
|
||||||
private openOrUpdateConfiguredProjectForFile(fileName: NormalizedPath, projectRootPath?: NormalizedPath): OpenConfiguredProjectResult {
|
private getConfigFileNameForFile(fileName: NormalizedPath, projectRootPath?: NormalizedPath) {
|
||||||
const searchPath = getDirectoryPath(fileName);
|
const searchPath = getDirectoryPath(fileName);
|
||||||
this.logger.info(`Search path: ${searchPath}`);
|
this.logger.info(`Search path: ${searchPath}`);
|
||||||
|
|
||||||
// check if this file is already included in one of external projects
|
// check if this file is already included in one of external projects
|
||||||
const configFileName = this.findConfigFile(asNormalizedPath(searchPath), projectRootPath);
|
const configFileName = this.findConfigFile(asNormalizedPath(searchPath), projectRootPath);
|
||||||
if (!configFileName) {
|
if (configFileName) {
|
||||||
this.logger.info("No config files found.");
|
this.logger.info(`Config file name: ${configFileName}`);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(`Config file name: ${configFileName}`);
|
|
||||||
|
|
||||||
const project = this.findConfiguredProjectByProjectName(configFileName);
|
|
||||||
if (!project) {
|
|
||||||
const { success, errors } = this.openConfigFile(configFileName, fileName);
|
|
||||||
if (!success) {
|
|
||||||
return { configFileName, configFileErrors: errors };
|
|
||||||
}
|
|
||||||
|
|
||||||
// even if opening config file was successful, it could still
|
|
||||||
// contain errors that were tolerated.
|
|
||||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
|
||||||
if (errors && errors.length > 0) {
|
|
||||||
return { configFileName, configFileErrors: errors };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.updateConfiguredProject(project);
|
this.logger.info("No config files found.");
|
||||||
}
|
}
|
||||||
|
return configFileName;
|
||||||
|
}
|
||||||
|
|
||||||
return { configFileName };
|
/**
|
||||||
|
* This function tries to search for a tsconfig.json for the given file. If we found it,
|
||||||
|
* we first detect if there is already a configured project created for it: if so, we re-read
|
||||||
|
* the tsconfig file content and update the project; otherwise we create a new one.
|
||||||
|
*/
|
||||||
|
private openOrUpdateConfiguredProjectForFile(fileName: NormalizedPath) {
|
||||||
|
const configFileName = this.getConfigFileNameForFile(fileName);
|
||||||
|
if (configFileName) {
|
||||||
|
const project = this.findConfiguredProjectByProjectName(configFileName);
|
||||||
|
if (!project) {
|
||||||
|
this.openConfigFile(configFileName, fileName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.updateConfiguredProject(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is different from the method the compiler uses because
|
// This is different from the method the compiler uses because
|
||||||
|
@ -971,7 +959,7 @@ namespace ts.server {
|
||||||
return findProjectByName(projectFileName, this.externalProjects);
|
return findProjectByName(projectFileName, this.externalProjects);
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertConfigFileContentToProjectOptions(configFilename: string): ConfigFileConversionResult {
|
private convertConfigFileContentToProjectOptions(configFilename: string) {
|
||||||
configFilename = normalizePath(configFilename);
|
configFilename = normalizePath(configFilename);
|
||||||
|
|
||||||
const configFileContent = this.host.readFile(configFilename);
|
const configFileContent = this.host.readFile(configFilename);
|
||||||
|
@ -996,23 +984,38 @@ namespace ts.server {
|
||||||
|
|
||||||
Debug.assert(!!parsedCommandLine.fileNames);
|
Debug.assert(!!parsedCommandLine.fileNames);
|
||||||
|
|
||||||
|
let success: boolean;
|
||||||
|
let projectOptions: ProjectOptions;
|
||||||
|
|
||||||
if (parsedCommandLine.fileNames.length === 0) {
|
if (parsedCommandLine.fileNames.length === 0) {
|
||||||
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||||
return { success: false, configFileErrors: errors };
|
success = false;
|
||||||
|
projectOptions = {
|
||||||
|
files: [],
|
||||||
|
compilerOptions: {},
|
||||||
|
configHasExtendsProperty: false,
|
||||||
|
configHasFilesProperty: false,
|
||||||
|
configHasIncludeProperty: false,
|
||||||
|
configHasExcludeProperty: false,
|
||||||
|
typeAcquisition: { enable: false }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
success = true;
|
||||||
|
projectOptions = {
|
||||||
|
files: parsedCommandLine.fileNames,
|
||||||
|
compilerOptions: parsedCommandLine.options,
|
||||||
|
configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined,
|
||||||
|
configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined,
|
||||||
|
configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined,
|
||||||
|
configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined,
|
||||||
|
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories),
|
||||||
|
typeAcquisition: parsedCommandLine.typeAcquisition,
|
||||||
|
compileOnSave: parsedCommandLine.compileOnSave
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectOptions: ProjectOptions = {
|
return { success, projectOptions, configFileErrors: errors };
|
||||||
files: parsedCommandLine.fileNames,
|
|
||||||
compilerOptions: parsedCommandLine.options,
|
|
||||||
configHasExtendsProperty: parsedCommandLine.raw["extends"] !== undefined,
|
|
||||||
configHasFilesProperty: parsedCommandLine.raw["files"] !== undefined,
|
|
||||||
configHasIncludeProperty: parsedCommandLine.raw["include"] !== undefined,
|
|
||||||
configHasExcludeProperty: parsedCommandLine.raw["exclude"] !== undefined,
|
|
||||||
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories),
|
|
||||||
typeAcquisition: parsedCommandLine.typeAcquisition,
|
|
||||||
compileOnSave: parsedCommandLine.compileOnSave
|
|
||||||
};
|
|
||||||
return { success: true, projectOptions, configFileErrors: errors };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private exceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader<T>) {
|
private exceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader<T>) {
|
||||||
|
@ -1149,8 +1152,7 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void {
|
private addFilesToProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, clientFileName: string, typeAcquisition: TypeAcquisition, configFileErrors: Diagnostic[]): void {
|
||||||
let errors: Diagnostic[];
|
for (const f of files) {
|
||||||
for (const f of files) {
|
|
||||||
const rootFilename = propertyReader.getFileName(f);
|
const rootFilename = propertyReader.getFileName(f);
|
||||||
const scriptKind = propertyReader.getScriptKind(f);
|
const scriptKind = propertyReader.getScriptKind(f);
|
||||||
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
const hasMixedContent = propertyReader.hasMixedContent(f, this.hostConfiguration.extraFileExtensions);
|
||||||
|
@ -1159,25 +1161,21 @@ namespace ts.server {
|
||||||
project.addRoot(info);
|
project.addRoot(info);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
(errors || (errors = [])).push(createFileNotFoundDiagnostic(rootFilename));
|
(configFileErrors || (configFileErrors = [])).push(createFileNotFoundDiagnostic(rootFilename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
project.setProjectErrors(concatenate(configFileErrors, errors));
|
project.setProjectErrors(configFileErrors);
|
||||||
project.setTypeAcquisition(typeAcquisition);
|
project.setTypeAcquisition(typeAcquisition);
|
||||||
project.updateGraph();
|
project.updateGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string): OpenConfigFileResult {
|
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
|
||||||
const conversionResult = this.convertConfigFileContentToProjectOptions(configFileName);
|
const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(configFileName);
|
||||||
const projectOptions: ProjectOptions = conversionResult.success
|
if (success) {
|
||||||
? conversionResult.projectOptions
|
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||||
: { files: [], compilerOptions: {}, configHasExtendsProperty: false, configHasFilesProperty: false, configHasIncludeProperty: false, configHasExcludeProperty: false, typeAcquisition: { enable: false } };
|
}
|
||||||
const project = this.createAndAddConfiguredProject(configFileName, projectOptions, conversionResult.configFileErrors, clientFileName);
|
|
||||||
return {
|
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, clientFileName);
|
||||||
success: conversionResult.success,
|
|
||||||
project,
|
|
||||||
errors: project.getGlobalProjectErrors()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||||
|
@ -1392,6 +1390,8 @@ namespace ts.server {
|
||||||
}
|
}
|
||||||
if (args.extraFileExtensions) {
|
if (args.extraFileExtensions) {
|
||||||
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
|
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
|
||||||
|
// TODO: (sheetalkamat) 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
|
||||||
this.logger.info("Host file extension mappings updated");
|
this.logger.info("Host file extension mappings updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1465,9 +1465,20 @@ namespace ts.server {
|
||||||
|
|
||||||
let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName);
|
let project: ConfiguredProject | ExternalProject = this.findContainingExternalProject(fileName);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
({ configFileName, configFileErrors } = this.openOrUpdateConfiguredProjectForFile(fileName, projectRootPath));
|
configFileName = this.getConfigFileNameForFile(fileName, projectRootPath);
|
||||||
if (configFileName) {
|
if (configFileName) {
|
||||||
project = this.findConfiguredProjectByProjectName(configFileName);
|
project = this.findConfiguredProjectByProjectName(configFileName);
|
||||||
|
if (!project) {
|
||||||
|
project = this.openConfigFile(configFileName, fileName);
|
||||||
|
|
||||||
|
// even if opening config file was successful, it could still
|
||||||
|
// contain errors that were tolerated.
|
||||||
|
const errors = project.getGlobalProjectErrors();
|
||||||
|
if (errors && errors.length > 0) {
|
||||||
|
// set configFileErrors only when the errors array is non-empty
|
||||||
|
configFileErrors = errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (project && !project.languageServiceEnabled) {
|
if (project && !project.languageServiceEnabled) {
|
||||||
|
@ -1792,9 +1803,8 @@ namespace ts.server {
|
||||||
for (const tsconfigFile of tsConfigFiles) {
|
for (const tsconfigFile of tsConfigFiles) {
|
||||||
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
|
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
const result = this.openConfigFile(tsconfigFile);
|
// errors are stored in the project
|
||||||
// TODO: save errors
|
project = this.openConfigFile(tsconfigFile);
|
||||||
project = result.success && result.project;
|
|
||||||
}
|
}
|
||||||
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
|
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
|
||||||
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
|
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
|
||||||
|
|
|
@ -626,6 +626,10 @@ namespace ts.server {
|
||||||
for (const missingFilePath of missingFilePaths) {
|
for (const missingFilePath of missingFilePaths) {
|
||||||
if (!this.missingFilesMap.has(missingFilePath)) {
|
if (!this.missingFilesMap.has(missingFilePath)) {
|
||||||
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
|
const fileWatcher = this.projectService.host.watchFile(missingFilePath, (_filename: string, eventKind: FileWatcherEventKind) => {
|
||||||
|
// TODO: (sheetalkamat) This needs to be fixed because of the way we create the projects
|
||||||
|
// Eg. ConfiguredProject and ExternalProject add roots only for existing files
|
||||||
|
// What this means is that if the file is not present when creating the project
|
||||||
|
// the program structure will not change because we created wrong program.
|
||||||
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
||||||
fileWatcher.close();
|
fileWatcher.close();
|
||||||
this.missingFilesMap.delete(missingFilePath);
|
this.missingFilesMap.delete(missingFilePath);
|
||||||
|
|
Loading…
Reference in a new issue