mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-29 16:29:19 -05:00
Only update file list when there are changes in the watched directories
This commit is contained in:
@@ -1421,7 +1421,7 @@ namespace ts {
|
||||
const options = extend(existingOptions, parsedConfig.options || {});
|
||||
options.configFilePath = configFileName;
|
||||
setConfigFileInOptions(options, sourceFile);
|
||||
const { fileNames, wildcardDirectories } = getFileNames();
|
||||
const { fileNames, wildcardDirectories, spec } = getFileNames();
|
||||
return {
|
||||
options,
|
||||
fileNames,
|
||||
@@ -1429,15 +1429,16 @@ namespace ts {
|
||||
raw,
|
||||
errors,
|
||||
wildcardDirectories,
|
||||
compileOnSave: !!raw.compileOnSave
|
||||
compileOnSave: !!raw.compileOnSave,
|
||||
configFileSpecs: spec
|
||||
};
|
||||
|
||||
function getFileNames(): ExpandResult {
|
||||
let fileNames: string[];
|
||||
let filesSpecs: string[];
|
||||
if (hasProperty(raw, "files")) {
|
||||
if (isArray(raw["files"])) {
|
||||
fileNames = <string[]>raw["files"];
|
||||
if (fileNames.length === 0) {
|
||||
filesSpecs = <string[]>raw["files"];
|
||||
if (filesSpecs.length === 0) {
|
||||
createCompilerDiagnosticOnlyIfJson(Diagnostics.The_files_list_in_config_file_0_is_empty, configFileName || "tsconfig.json");
|
||||
}
|
||||
}
|
||||
@@ -1475,19 +1476,14 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
if (fileNames === undefined && includeSpecs === undefined) {
|
||||
if (filesSpecs === undefined && includeSpecs === undefined) {
|
||||
includeSpecs = ["**/*"];
|
||||
}
|
||||
|
||||
const result = matchFileNames(fileNames, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile);
|
||||
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, basePath, options, host, errors, extraFileExtensions, sourceFile);
|
||||
|
||||
if (result.fileNames.length === 0 && !hasProperty(raw, "files") && resolutionStack.length === 0) {
|
||||
errors.push(
|
||||
createCompilerDiagnostic(
|
||||
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
|
||||
configFileName || "tsconfig.json",
|
||||
JSON.stringify(includeSpecs || []),
|
||||
JSON.stringify(excludeSpecs || [])));
|
||||
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1500,6 +1496,14 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function getErrorForNoInputFiles({ includeSpecs, excludeSpecs }: ConfigFileSpecs, configFileName?: string) {
|
||||
return createCompilerDiagnostic(
|
||||
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2,
|
||||
configFileName || "tsconfig.json",
|
||||
JSON.stringify(includeSpecs || []),
|
||||
JSON.stringify(excludeSpecs || []));
|
||||
}
|
||||
|
||||
interface ParsedTsconfig {
|
||||
raw: any;
|
||||
options?: CompilerOptions;
|
||||
@@ -1946,7 +1950,40 @@ namespace ts {
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
function matchFileNames(fileNames: string[], include: string[], exclude: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], extraFileExtensions: JsFileExtensionInfo[], jsonSourceFile: JsonSourceFile): ExpandResult {
|
||||
function matchFileNames(filesSpecs: string[], includeSpecs: string[], excludeSpecs: string[], basePath: string, options: CompilerOptions, host: ParseConfigHost, errors: Diagnostic[], extraFileExtensions: JsFileExtensionInfo[], jsonSourceFile: JsonSourceFile): ExpandResult {
|
||||
basePath = normalizePath(basePath);
|
||||
let validatedIncludeSpecs: string[], validatedExcludeSpecs: string[];
|
||||
|
||||
if (includeSpecs) {
|
||||
validatedIncludeSpecs = validateSpecs(includeSpecs, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
|
||||
}
|
||||
|
||||
if (excludeSpecs) {
|
||||
validatedExcludeSpecs = validateSpecs(excludeSpecs, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
|
||||
}
|
||||
|
||||
// Wildcard directories (provided as part of a wildcard path) are stored in a
|
||||
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
|
||||
// or a recursive directory. This information is used by filesystem watchers to monitor for
|
||||
// new entries in these paths.
|
||||
const wildcardDirectories = getWildcardDirectories(validatedIncludeSpecs, validatedExcludeSpecs, basePath, host.useCaseSensitiveFileNames);
|
||||
|
||||
const spec: ConfigFileSpecs = { filesSpecs, includeSpecs, excludeSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories };
|
||||
return getFileNamesFromConfigSpecs(spec, basePath, options, host, extraFileExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands an array of file specifications.
|
||||
*
|
||||
* @param fileNames The literal file names to include.
|
||||
* @param include The wildcard file specifications to include.
|
||||
* @param exclude The wildcard file specifications to exclude.
|
||||
* @param basePath The base path for any relative file specifications.
|
||||
* @param options Compiler options.
|
||||
* @param host The host used to resolve files and directories.
|
||||
* @param errors An array for diagnostic reporting.
|
||||
*/
|
||||
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: JsFileExtensionInfo[]): ExpandResult {
|
||||
basePath = normalizePath(basePath);
|
||||
|
||||
// The exclude spec list is converted into a regular expression, which allows us to quickly
|
||||
@@ -1964,19 +2001,7 @@ namespace ts {
|
||||
// via wildcard, and to handle extension priority.
|
||||
const wildcardFileMap = createMap<string>();
|
||||
|
||||
if (include) {
|
||||
include = validateSpecs(include, errors, /*allowTrailingRecursion*/ false, jsonSourceFile, "include");
|
||||
}
|
||||
|
||||
if (exclude) {
|
||||
exclude = validateSpecs(exclude, errors, /*allowTrailingRecursion*/ true, jsonSourceFile, "exclude");
|
||||
}
|
||||
|
||||
// Wildcard directories (provided as part of a wildcard path) are stored in a
|
||||
// file map that marks whether it was a regular wildcard match (with a `*` or `?` token),
|
||||
// or a recursive directory. This information is used by filesystem watchers to monitor for
|
||||
// new entries in these paths.
|
||||
const wildcardDirectories = getWildcardDirectories(include, exclude, basePath, host.useCaseSensitiveFileNames);
|
||||
const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec;
|
||||
|
||||
// Rather than requery this for each file and filespec, we query the supported extensions
|
||||
// once and store it on the expansion context.
|
||||
@@ -1984,15 +2009,15 @@ namespace ts {
|
||||
|
||||
// Literal files are always included verbatim. An "include" or "exclude" specification cannot
|
||||
// remove a literal file.
|
||||
if (fileNames) {
|
||||
for (const fileName of fileNames) {
|
||||
if (filesSpecs) {
|
||||
for (const fileName of filesSpecs) {
|
||||
const file = combinePaths(basePath, fileName);
|
||||
literalFileMap.set(keyMapper(file), file);
|
||||
}
|
||||
}
|
||||
|
||||
if (include && include.length > 0) {
|
||||
for (const file of host.readDirectory(basePath, supportedExtensions, exclude, include, /*depth*/ undefined)) {
|
||||
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) {
|
||||
for (const file of host.readDirectory(basePath, supportedExtensions, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
|
||||
// If we have already included a literal or wildcard path with a
|
||||
// higher priority extension, we should skip this file.
|
||||
//
|
||||
@@ -2020,7 +2045,8 @@ namespace ts {
|
||||
const wildcardFiles = arrayFrom(wildcardFileMap.values());
|
||||
return {
|
||||
fileNames: literalFiles.concat(wildcardFiles),
|
||||
wildcardDirectories
|
||||
wildcardDirectories,
|
||||
spec
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3731,6 +3731,7 @@ namespace ts {
|
||||
errors: Diagnostic[];
|
||||
wildcardDirectories?: MapLike<WatchDirectoryFlags>;
|
||||
compileOnSave?: boolean;
|
||||
configFileSpecs?: ConfigFileSpecs;
|
||||
}
|
||||
|
||||
export const enum WatchDirectoryFlags {
|
||||
@@ -3738,9 +3739,19 @@ namespace ts {
|
||||
Recursive = 1 << 0,
|
||||
}
|
||||
|
||||
export interface ConfigFileSpecs {
|
||||
filesSpecs: string[];
|
||||
includeSpecs: string[];
|
||||
excludeSpecs: string[];
|
||||
validatedIncludeSpecs: string[];
|
||||
validatedExcludeSpecs: string[];
|
||||
wildcardDirectories: MapLike<WatchDirectoryFlags>;
|
||||
}
|
||||
|
||||
export interface ExpandResult {
|
||||
fileNames: string[];
|
||||
wildcardDirectories: MapLike<WatchDirectoryFlags>;
|
||||
spec: ConfigFileSpecs;
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
|
||||
@@ -1774,8 +1774,7 @@ namespace ts.projectSystem {
|
||||
|
||||
host.reloadFS([file1, file2, configFile]);
|
||||
|
||||
host.checkTimeoutQueueLength(1);
|
||||
host.runQueuedTimeoutCallbacks(); // to execute throttled requests
|
||||
host.checkTimeoutQueueLength(0); // TODO: update graph scheduling (instead of instant update graph)
|
||||
|
||||
checkNumberOfProjects(projectService, { configuredProjects: 1 });
|
||||
checkProjectRootFiles(projectService.configuredProjects[0], [file1.path, file2.path]);
|
||||
|
||||
@@ -617,33 +617,30 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
this.logger.info(`Detected source file changes: ${fileName}`);
|
||||
this.throttledOperations.schedule(
|
||||
project.getConfigFilePath(),
|
||||
/*delay*/250,
|
||||
() => this.handleFileAddOrRemoveInWatchedDirectoryOfProject(project, fileName));
|
||||
}
|
||||
|
||||
private handleFileAddOrRemoveInWatchedDirectoryOfProject(project: ConfiguredProject, triggerFile: string) {
|
||||
// TODO: (sheetalkamat) this actually doesnt need to re-read the config file from the disk
|
||||
// it just needs to update the file list of file names
|
||||
// We might be able to do that by caching the info from first parse and add reusing this with the change in the file path
|
||||
const { projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
this.reportConfigFileDiagnostics(project.getProjectName(), configFileErrors, triggerFile);
|
||||
|
||||
const newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f)));
|
||||
const currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f)));
|
||||
|
||||
// We check if the project file list has changed. If so, we update the project.
|
||||
if (!arrayIsEqualTo(currentRootFiles.sort(), newRootFiles.sort())) {
|
||||
// For configured projects, the change is made outside the tsconfig file, and
|
||||
// it is not likely to affect the project for other files opened by the client. We can
|
||||
// just update the current project.
|
||||
|
||||
this.logger.info("Updating configured project");
|
||||
this.updateConfiguredProject(project, projectOptions, configFileErrors);
|
||||
|
||||
// Call refreshInferredProjects to clean up inferred projects we may have
|
||||
// created for the new files
|
||||
const configFileSpecs = project.configFileSpecs;
|
||||
const configFilename = normalizePath(project.getConfigFilePath());
|
||||
// TODO: (sheetalkamat) use the host that caches - so we dont do file exists and read directory call
|
||||
const result = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFilename), project.getCompilerOptions(), this.host, this.hostConfiguration.extraFileExtensions);
|
||||
const errors = project.getAllProjectErrors();
|
||||
if (result.fileNames.length === 0) {
|
||||
if (!configFileSpecs.filesSpecs) {
|
||||
if (!some(errors, error => error.code === Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code)) {
|
||||
errors.push(getErrorForNoInputFiles(configFileSpecs, configFilename));
|
||||
}
|
||||
if (!some(errors, error => error.code === Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files.code)) {
|
||||
errors.push(createCompilerDiagnostic(Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files, configFilename));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
filterMutate(errors, error =>
|
||||
error.code !== Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code &&
|
||||
error.code !== Diagnostics.The_config_file_0_found_doesn_t_contain_any_source_files.code);
|
||||
}
|
||||
this.updateNonInferredProjectFiles(project, result.fileNames, fileNamePropertyReader);
|
||||
// TODO: (sheetalkamat) schedule the update graph
|
||||
if (!project.updateGraph()) {
|
||||
this.refreshInferredProjects();
|
||||
}
|
||||
}
|
||||
@@ -684,11 +681,6 @@ namespace ts.server {
|
||||
this.reloadProjects();
|
||||
}
|
||||
|
||||
private getCanonicalFileName(fileName: string) {
|
||||
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
|
||||
return normalizePath(name);
|
||||
}
|
||||
|
||||
private removeProject(project: Project) {
|
||||
this.logger.info(`remove project: ${project.getRootFiles().toString()}`);
|
||||
|
||||
@@ -1009,7 +1001,7 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
return { success, projectOptions, configFileErrors: errors };
|
||||
return { success, projectOptions, configFileErrors: errors, configFileSpecs: parsedCommandLine.configFileSpecs };
|
||||
}
|
||||
|
||||
private exceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions, fileNames: T[], propertyReader: FilePropertyReader<T>) {
|
||||
@@ -1113,7 +1105,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], clientFileName?: string) {
|
||||
private createAndAddConfiguredProject(configFileName: NormalizedPath, projectOptions: ProjectOptions, configFileErrors: Diagnostic[], configFileSpecs: ConfigFileSpecs, clientFileName?: string) {
|
||||
const sizeLimitExceeded = this.exceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files, fileNamePropertyReader);
|
||||
const project = new ConfiguredProject(
|
||||
configFileName,
|
||||
@@ -1126,6 +1118,7 @@ namespace ts.server {
|
||||
|
||||
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));
|
||||
@@ -1156,15 +1149,15 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private openConfigFile(configFileName: NormalizedPath, clientFileName?: string) {
|
||||
const { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(configFileName);
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName);
|
||||
if (success) {
|
||||
this.logger.info(`Opened configuration file ${configFileName}`);
|
||||
}
|
||||
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, clientFileName);
|
||||
return this.createAndAddConfiguredProject(configFileName, projectOptions, configFileErrors, configFileSpecs, clientFileName);
|
||||
}
|
||||
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>) {
|
||||
const projectRootFilesMap = project.getRootFilesMap();
|
||||
const newRootScriptInfoMap: Map<ProjectRoot> = createMap<ProjectRoot>();
|
||||
|
||||
@@ -1219,7 +1212,10 @@ namespace ts.server {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean, configFileErrors: Diagnostic[]) {
|
||||
this.updateNonInferredProjectFiles(project, newUncheckedFiles, propertyReader);
|
||||
project.setCompilerOptions(newOptions);
|
||||
project.setTypeAcquisition(newTypeAcquisition);
|
||||
|
||||
@@ -1243,7 +1239,8 @@ namespace ts.server {
|
||||
// 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 { success, projectOptions, configFileErrors } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
const { success, projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(project.getConfigFilePath());
|
||||
project.configFileSpecs = configFileSpecs;
|
||||
if (!success) {
|
||||
// reset project settings to default
|
||||
this.updateNonInferredProject(project, [], fileNamePropertyReader, {}, {}, /*compileOnSave*/ false, configFileErrors);
|
||||
|
||||
@@ -971,6 +971,9 @@ namespace ts.server {
|
||||
private typeRootsWatchers: FileWatcher[];
|
||||
readonly canonicalConfigFilePath: NormalizedPath;
|
||||
|
||||
/*@internal*/
|
||||
configFileSpecs: ConfigFileSpecs;
|
||||
|
||||
private plugins: PluginModule[] = [];
|
||||
|
||||
/** Used for configured projects which may have multiple open roots */
|
||||
|
||||
Reference in New Issue
Block a user